]> git.proxmox.com Git - ceph.git/blobdiff - ceph/src/osd/OSD.cc
import ceph pacific 16.2.5
[ceph.git] / ceph / src / osd / OSD.cc
index e6d18ecd2df6702ee997928f8df63ad72a133df5..f580fb6f5645280c7357ade9585e2e64677f1340 100644 (file)
  * Foundation.  See file COPYING.
  *
  */
+
 #include "acconfig.h"
 
+#include <cctype>
 #include <fstream>
 #include <iostream>
-#include <errno.h>
+#include <iterator>
+
+#include <unistd.h>
 #include <sys/stat.h>
 #include <signal.h>
-#include <ctype.h>
+#include <time.h>
 #include <boost/scoped_ptr.hpp>
+#include <boost/range/adaptor/reversed.hpp>
 
 #ifdef HAVE_SYS_PARAM_H
 #include <sys/param.h>
 #endif
 
 #include "osd/PG.h"
+#include "osd/scrub_machine.h"
+#include "osd/pg_scrubber.h"
 
 #include "include/types.h"
 #include "include/compat.h"
+#include "include/random.h"
 
 #include "OSD.h"
 #include "OSDMap.h"
 
 #include "common/errno.h"
 #include "common/ceph_argparse.h"
+#include "common/ceph_releases.h"
+#include "common/ceph_time.h"
 #include "common/version.h"
-#include "common/io_priority.h"
+#include "common/async/blocked_completion.h"
+#include "common/pick_address.h"
+#include "common/blkdev.h"
+#include "common/numa.h"
 
 #include "os/ObjectStore.h"
 #ifdef HAVE_LIBFUSE
@@ -52,7 +65,6 @@
 
 #include "PrimaryLogPG.h"
 
-
 #include "msg/Messenger.h"
 #include "msg/Message.h"
 
 #include "messages/MLog.h"
 
 #include "messages/MGenericMessage.h"
-#include "messages/MPing.h"
 #include "messages/MOSDPing.h"
 #include "messages/MOSDFailure.h"
 #include "messages/MOSDMarkMeDown.h"
+#include "messages/MOSDMarkMeDead.h"
 #include "messages/MOSDFull.h"
 #include "messages/MOSDOp.h"
 #include "messages/MOSDOpReply.h"
 #include "messages/MOSDRepOpReply.h"
 #include "messages/MOSDBoot.h"
 #include "messages/MOSDPGTemp.h"
+#include "messages/MOSDPGReadyToMerge.h"
 
 #include "messages/MOSDMap.h"
 #include "messages/MMonGetOSDMap.h"
 #include "messages/MOSDPGNotify.h"
+#include "messages/MOSDPGNotify2.h"
 #include "messages/MOSDPGQuery.h"
+#include "messages/MOSDPGQuery2.h"
 #include "messages/MOSDPGLog.h"
 #include "messages/MOSDPGRemove.h"
 #include "messages/MOSDPGInfo.h"
+#include "messages/MOSDPGInfo2.h"
 #include "messages/MOSDPGCreate.h"
-#include "messages/MOSDPGTrim.h"
-#include "messages/MOSDPGScan.h"
-#include "messages/MOSDPGBackfill.h"
+#include "messages/MOSDPGCreate2.h"
 #include "messages/MBackfillReserve.h"
 #include "messages/MRecoveryReserve.h"
+#include "messages/MOSDForceRecovery.h"
 #include "messages/MOSDECSubOpWrite.h"
 #include "messages/MOSDECSubOpWriteReply.h"
 #include "messages/MOSDECSubOpRead.h"
 #include "messages/MOSDPGUpdateLogMissing.h"
 #include "messages/MOSDPGUpdateLogMissingReply.h"
 
+#include "messages/MOSDPeeringOp.h"
+
 #include "messages/MOSDAlive.h"
 
 #include "messages/MOSDScrub.h"
-#include "messages/MOSDScrubReserve.h"
+#include "messages/MOSDScrub2.h"
 #include "messages/MOSDRepScrub.h"
 
-#include "messages/MMonCommand.h"
 #include "messages/MCommand.h"
 #include "messages/MCommandReply.h"
 
 #include "messages/MPGStats.h"
-#include "messages/MPGStatsAck.h"
 
 #include "messages/MWatchNotify.h"
 #include "messages/MOSDPGPush.h"
 #include "messages/MOSDPGPushReply.h"
 #include "messages/MOSDPGPull.h"
 
+#include "messages/MMonGetPurgedSnaps.h"
+#include "messages/MMonGetPurgedSnapsReply.h"
+
 #include "common/perf_counters.h"
 #include "common/Timer.h"
 #include "common/LogClient.h"
 #include "perfglue/cpu_profiler.h"
 #include "perfglue/heap_profiler.h"
 
+#include "osd/ClassHandler.h"
 #include "osd/OpRequest.h"
 
 #include "auth/AuthAuthorizeHandler.h"
 #include "auth/RotatingKeyRing.h"
-#include "common/errno.h"
 
 #include "objclass/objclass.h"
 
 #include "include/str_list.h"
 #include "include/util.h"
 
-#include "include/assert.h"
+#include "include/ceph_assert.h"
 #include "common/config.h"
 #include "common/EventTrace.h"
 
+#include "json_spirit/json_spirit_reader.h"
+#include "json_spirit/json_spirit_writer.h"
+
 #ifdef WITH_LTTNG
 #define TRACEPOINT_DEFINE
 #define TRACEPOINT_PROBE_DYNAMIC_LINKAGE
 #else
 #define tracepoint(...)
 #endif
+#ifdef HAVE_JAEGER
+#include "common/tracer.h"
+#endif
 
 #define dout_context cct
 #define dout_subsys ceph_subsys_osd
 #undef dout_prefix
 #define dout_prefix _prefix(_dout, whoami, get_osdmap_epoch())
 
-const double OSD::OSD_TICK_INTERVAL = 1.0;
+using std::deque;
+using std::list;
+using std::lock_guard;
+using std::make_pair;
+using std::make_tuple;
+using std::make_unique;
+using std::map;
+using std::ostream;
+using std::ostringstream;
+using std::pair;
+using std::set;
+using std::string;
+using std::stringstream;
+using std::to_string;
+using std::unique_ptr;
+using std::vector;
+
+using ceph::bufferlist;
+using ceph::bufferptr;
+using ceph::decode;
+using ceph::encode;
+using ceph::fixed_u_to_string;
+using ceph::Formatter;
+using ceph::heartbeat_handle_d;
+using ceph::make_mutex;
+
+using namespace ceph::osd::scheduler;
+using TOPNSPC::common::cmd_getval;
 
 static ostream& _prefix(std::ostream* _dout, int whoami, epoch_t epoch) {
   return *_dout << "osd." << whoami << " " << epoch << " ";
 }
 
-void PGQueueable::RunVis::operator()(const OpRequestRef &op) {
-  return osd->dequeue_op(pg, op, handle);
-}
-
-void PGQueueable::RunVis::operator()(const PGSnapTrim &op) {
-  return pg->snap_trimmer(op.epoch_queued);
-}
-
-void PGQueueable::RunVis::operator()(const PGScrub &op) {
-  return pg->scrub(op.epoch_queued, handle);
-}
-
-void PGQueueable::RunVis::operator()(const PGRecovery &op) {
-  return osd->do_recovery(pg.get(), op.epoch_queued, op.reserved_pushes, handle);
-}
-
 //Initial features in new superblock.
 //Features here are also automatically upgraded
 CompatSet OSD::get_osd_initial_compat_set() {
@@ -202,6 +237,8 @@ CompatSet OSD::get_osd_initial_compat_set() {
   ceph_osd_feature_incompat.insert(CEPH_OSD_FEATURE_INCOMPAT_PGMETA);
   ceph_osd_feature_incompat.insert(CEPH_OSD_FEATURE_INCOMPAT_MISSING);
   ceph_osd_feature_incompat.insert(CEPH_OSD_FEATURE_INCOMPAT_FASTINFO);
+  ceph_osd_feature_incompat.insert(CEPH_OSD_FEATURE_INCOMPAT_RECOVERY_DELETES);
+  ceph_osd_feature_incompat.insert(CEPH_OSD_FEATURE_INCOMPAT_SNAPMAPPER2);
   return CompatSet(ceph_osd_feature_compat, ceph_osd_feature_ro_compat,
                   ceph_osd_feature_incompat);
 }
@@ -214,10 +251,9 @@ CompatSet OSD::get_osd_compat_set() {
   return compat;
 }
 
-OSDService::OSDService(OSD *osd) :
+OSDService::OSDService(OSD *osd, ceph::async::io_context_pool& poolctx) :
   osd(osd),
   cct(osd->cct),
-  meta_osr(new ObjectStore::Sequencer("meta")),
   whoami(osd->whoami), store(osd->store),
   log_client(osd->log_client), clog(osd->clog),
   pg_recovery_stats(osd->pg_recovery_stats),
@@ -226,291 +262,280 @@ OSDService::OSDService(OSD *osd) :
   logger(osd->logger),
   recoverystate_perf(osd->recoverystate_perf),
   monc(osd->monc),
-  peering_wq(osd->peering_wq),
-  recovery_gen_wq("recovery_gen_wq", cct->_conf->osd_recovery_thread_timeout,
-                 &osd->disk_tp),
-  class_handler(osd->class_handler),
-  pg_epoch_lock("OSDService::pg_epoch_lock"),
-  publish_lock("OSDService::publish_lock"),
-  pre_publish_lock("OSDService::pre_publish_lock"),
+  osd_max_object_size(cct->_conf, "osd_max_object_size"),
+  osd_skip_data_digest(cct->_conf, "osd_skip_data_digest"),
+  publish_lock{ceph::make_mutex("OSDService::publish_lock")},
+  pre_publish_lock{ceph::make_mutex("OSDService::pre_publish_lock")},
   max_oldest_map(0),
-  peer_map_epoch_lock("OSDService::peer_map_epoch_lock"),
-  sched_scrub_lock("OSDService::sched_scrub_lock"), scrubs_pending(0),
-  scrubs_active(0),
-  agent_lock("OSDService::agent_lock"),
+  scrubs_local(0),
+  scrubs_remote(0),
   agent_valid_iterator(false),
   agent_ops(0),
   flush_mode_high_count(0),
   agent_active(true),
   agent_thread(this),
   agent_stop_flag(false),
-  agent_timer_lock("OSDService::agent_timer_lock"),
   agent_timer(osd->client_messenger->cct, agent_timer_lock),
   last_recalibrate(ceph_clock_now()),
   promote_max_objects(0),
   promote_max_bytes(0),
-  objecter(new Objecter(osd->client_messenger->cct, osd->objecter_messenger, osd->monc, NULL, 0, 0)),
-  objecter_finisher(osd->client_messenger->cct),
-  watch_lock("OSDService::watch_lock"),
+  poolctx(poolctx),
+  objecter(make_unique<Objecter>(osd->client_messenger->cct,
+                                osd->objecter_messenger,
+                                osd->monc, poolctx)),
+  m_objecter_finishers(cct->_conf->osd_objecter_finishers),
   watch_timer(osd->client_messenger->cct, watch_lock),
   next_notif_id(0),
-  recovery_request_lock("OSDService::recovery_request_lock"),
   recovery_request_timer(cct, recovery_request_lock, false),
+  sleep_timer(cct, sleep_lock, false),
   reserver_finisher(cct),
-  local_reserver(&reserver_finisher, cct->_conf->osd_max_backfills,
+  local_reserver(cct, &reserver_finisher, cct->_conf->osd_max_backfills,
                 cct->_conf->osd_min_recovery_priority),
-  remote_reserver(&reserver_finisher, cct->_conf->osd_max_backfills,
+  remote_reserver(cct, &reserver_finisher, cct->_conf->osd_max_backfills,
                  cct->_conf->osd_min_recovery_priority),
-  pg_temp_lock("OSDService::pg_temp_lock"),
-  snap_sleep_lock("OSDService::snap_sleep_lock"),
-  snap_sleep_timer(
-    osd->client_messenger->cct, snap_sleep_lock, false /* relax locking */),
-  snap_reserver(&reserver_finisher,
+  snap_reserver(cct, &reserver_finisher,
                cct->_conf->osd_max_trimming_pgs),
-  recovery_lock("OSDService::recovery_lock"),
   recovery_ops_active(0),
   recovery_ops_reserved(0),
   recovery_paused(false),
-  map_cache_lock("OSDService::map_cache_lock"),
   map_cache(cct, cct->_conf->osd_map_cache_size),
   map_bl_cache(cct->_conf->osd_map_cache_size),
   map_bl_inc_cache(cct->_conf->osd_map_cache_size),
-  in_progress_split_lock("OSDService::in_progress_split_lock"),
-  stat_lock("OSDService::stat_lock"),
-  full_status_lock("OSDService::full_status_lock"),
   cur_state(NONE),
-  cur_ratio(0),
-  epoch_lock("OSDService::epoch_lock"),
-  boot_epoch(0), up_epoch(0), bind_epoch(0),
-  is_stopping_lock("OSDService::is_stopping_lock")
-#ifdef PG_DEBUG_REFS
-  , pgid_lock("OSDService::pgid_lock")
-#endif
+  cur_ratio(0), physical_ratio(0),
+  boot_epoch(0), up_epoch(0), bind_epoch(0)
 {
   objecter->init();
-}
 
-OSDService::~OSDService()
-{
-  delete objecter;
+  for (int i = 0; i < m_objecter_finishers; i++) {
+    ostringstream str;
+    str << "objecter-finisher-" << i;
+    auto fin = make_unique<Finisher>(osd->client_messenger->cct, str.str(), "finisher");
+    objecter_finishers.push_back(std::move(fin));
+  }
 }
 
-void OSDService::_start_split(spg_t parent, const set<spg_t> &children)
+#ifdef PG_DEBUG_REFS
+void OSDService::add_pgid(spg_t pgid, PG *pg) {
+  std::lock_guard l(pgid_lock);
+  if (!pgid_tracker.count(pgid)) {
+    live_pgs[pgid] = pg;
+  }
+  pgid_tracker[pgid]++;
+}
+void OSDService::remove_pgid(spg_t pgid, PG *pg)
 {
-  for (set<spg_t>::const_iterator i = children.begin();
-       i != children.end();
-       ++i) {
-    dout(10) << __func__ << ": Starting split on pg " << *i
-            << ", parent=" << parent << dendl;
-    assert(!pending_splits.count(*i));
-    assert(!in_progress_splits.count(*i));
-    pending_splits.insert(make_pair(*i, parent));
-
-    assert(!rev_pending_splits[parent].count(*i));
-    rev_pending_splits[parent].insert(*i);
+  std::lock_guard l(pgid_lock);
+  ceph_assert(pgid_tracker.count(pgid));
+  ceph_assert(pgid_tracker[pgid] > 0);
+  pgid_tracker[pgid]--;
+  if (pgid_tracker[pgid] == 0) {
+    pgid_tracker.erase(pgid);
+    live_pgs.erase(pgid);
   }
 }
-
-void OSDService::mark_split_in_progress(spg_t parent, const set<spg_t> &children)
+void OSDService::dump_live_pgids()
 {
-  Mutex::Locker l(in_progress_split_lock);
-  map<spg_t, set<spg_t> >::iterator piter = rev_pending_splits.find(parent);
-  assert(piter != rev_pending_splits.end());
-  for (set<spg_t>::const_iterator i = children.begin();
-       i != children.end();
+  std::lock_guard l(pgid_lock);
+  derr << "live pgids:" << dendl;
+  for (map<spg_t, int>::const_iterator i = pgid_tracker.cbegin();
+       i != pgid_tracker.cend();
        ++i) {
-    assert(piter->second.count(*i));
-    assert(pending_splits.count(*i));
-    assert(!in_progress_splits.count(*i));
-    assert(pending_splits[*i] == parent);
-
-    pending_splits.erase(*i);
-    piter->second.erase(*i);
-    in_progress_splits.insert(*i);
+    derr << "\t" << *i << dendl;
+    live_pgs[i->first]->dump_live_ids();
   }
-  if (piter->second.empty())
-    rev_pending_splits.erase(piter);
 }
+#endif
+
 
-void OSDService::cancel_pending_splits_for_parent(spg_t parent)
+ceph::signedspan OSDService::get_mnow()
 {
-  Mutex::Locker l(in_progress_split_lock);
-  _cancel_pending_splits_for_parent(parent);
+  return ceph::mono_clock::now() - osd->startup_time;
 }
 
-void OSDService::_cancel_pending_splits_for_parent(spg_t parent)
+void OSDService::identify_splits_and_merges(
+  OSDMapRef old_map,
+  OSDMapRef new_map,
+  spg_t pgid,
+  set<pair<spg_t,epoch_t>> *split_children,
+  set<pair<spg_t,epoch_t>> *merge_pgs)
 {
-  map<spg_t, set<spg_t> >::iterator piter = rev_pending_splits.find(parent);
-  if (piter == rev_pending_splits.end())
+  if (!old_map->have_pg_pool(pgid.pool())) {
     return;
-
-  for (set<spg_t>::iterator i = piter->second.begin();
-       i != piter->second.end();
-       ++i) {
-    assert(pending_splits.count(*i));
-    assert(!in_progress_splits.count(*i));
-    pending_splits.erase(*i);
-    dout(10) << __func__ << ": Completing split on pg " << *i
-            << " for parent: " << parent << dendl;
-    _cancel_pending_splits_for_parent(*i);
   }
-  rev_pending_splits.erase(piter);
-}
-
-void OSDService::_maybe_split_pgid(OSDMapRef old_map,
-                                 OSDMapRef new_map,
-                                 spg_t pgid)
-{
-  assert(old_map->have_pg_pool(pgid.pool()));
   int old_pgnum = old_map->get_pg_num(pgid.pool());
-  if (pgid.ps() < static_cast<unsigned>(old_pgnum)) {
-    set<spg_t> children;
-    if (pgid.is_split(old_pgnum,
-                 new_map->get_pg_num(pgid.pool()), &children)) { 
-      _start_split(pgid, children); }
-  } else {
-    assert(pgid.ps() < static_cast<unsigned>(new_map->get_pg_num(pgid.pool())));
-  }
-}
-
-void OSDService::init_splits_between(spg_t pgid,
-                                    OSDMapRef frommap,
-                                    OSDMapRef tomap)
-{
-  // First, check whether we can avoid this potentially expensive check
-  if (tomap->have_pg_pool(pgid.pool()) &&
-      pgid.is_split(
-       frommap->get_pg_num(pgid.pool()),
-       tomap->get_pg_num(pgid.pool()),
-       NULL)) {
-    // Ok, a split happened, so we need to walk the osdmaps
-    set<spg_t> new_pgs; // pgs to scan on each map
-    new_pgs.insert(pgid);
-    OSDMapRef curmap(get_map(frommap->get_epoch()));
-    for (epoch_t e = frommap->get_epoch() + 1;
-        e <= tomap->get_epoch();
-        ++e) {
-      OSDMapRef nextmap(try_get_map(e));
-      if (!nextmap)
-       continue;
-      set<spg_t> even_newer_pgs; // pgs added in this loop
-      for (set<spg_t>::iterator i = new_pgs.begin(); i != new_pgs.end(); ++i) {
-       set<spg_t> split_pgs;
-       if (i->is_split(curmap->get_pg_num(i->pool()),
-                       nextmap->get_pg_num(i->pool()),
-                       &split_pgs)) {
-         start_split(*i, split_pgs);
-         even_newer_pgs.insert(split_pgs.begin(), split_pgs.end());
+  auto p = osd->pg_num_history.pg_nums.find(pgid.pool());
+  if (p == osd->pg_num_history.pg_nums.end()) {
+    return;
+  }
+  dout(20) << __func__ << " " << pgid << " e" << old_map->get_epoch()
+          << " to e" << new_map->get_epoch()
+          << " pg_nums " << p->second << dendl;
+  deque<spg_t> queue;
+  queue.push_back(pgid);
+  set<spg_t> did;
+  while (!queue.empty()) {
+    auto cur = queue.front();
+    queue.pop_front();
+    did.insert(cur);
+    unsigned pgnum = old_pgnum;
+    for (auto q = p->second.lower_bound(old_map->get_epoch());
+        q != p->second.end() &&
+          q->first <= new_map->get_epoch();
+        ++q) {
+      if (pgnum < q->second) {
+       // split?
+       if (cur.ps() < pgnum) {
+         set<spg_t> children;
+         if (cur.is_split(pgnum, q->second, &children)) {
+           dout(20) << __func__ << " " << cur << " e" << q->first
+                    << " pg_num " << pgnum << " -> " << q->second
+                    << " children " << children << dendl;
+           for (auto i : children) {
+             split_children->insert(make_pair(i, q->first));
+              if (!did.count(i))
+               queue.push_back(i);
+           }
+         }
+       } else if (cur.ps() < q->second) {
+         dout(20) << __func__ << " " << cur << " e" << q->first
+                  << " pg_num " << pgnum << " -> " << q->second
+                  << " is a child" << dendl;
+         // normally we'd capture this from the parent, but it's
+         // possible the parent doesn't exist yet (it will be
+         // fabricated to allow an intervening merge).  note this PG
+         // as a split child here to be sure we catch it.
+         split_children->insert(make_pair(cur, q->first));
+       } else {
+         dout(20) << __func__ << " " << cur << " e" << q->first
+                  << " pg_num " << pgnum << " -> " << q->second
+                  << " is post-split, skipping" << dendl;
+       }
+      } else if (merge_pgs) {
+       // merge?
+       if (cur.ps() >= q->second) {
+         if (cur.ps() < pgnum) {
+           spg_t parent;
+           if (cur.is_merge_source(pgnum, q->second, &parent)) {
+             set<spg_t> children;
+             parent.is_split(q->second, pgnum, &children);
+             dout(20) << __func__ << " " << cur << " e" << q->first
+                      << " pg_num " << pgnum << " -> " << q->second
+                      << " is merge source, target " << parent
+                      << ", source(s) " << children << dendl;
+             merge_pgs->insert(make_pair(parent, q->first));
+              if (!did.count(parent)) {
+                // queue (and re-scan) parent in case it might not exist yet
+                // and there are some future splits pending on it
+                queue.push_back(parent);
+              }
+             for (auto c : children) {
+               merge_pgs->insert(make_pair(c, q->first));
+                if (!did.count(c))
+                  queue.push_back(c);
+             }
+           }
+         } else {
+           dout(20) << __func__ << " " << cur << " e" << q->first
+                    << " pg_num " << pgnum << " -> " << q->second
+                    << " is beyond old pgnum, skipping" << dendl;
+         }
+       } else {
+         set<spg_t> children;
+         if (cur.is_split(q->second, pgnum, &children)) {
+           dout(20) << __func__ << " " << cur << " e" << q->first
+                    << " pg_num " << pgnum << " -> " << q->second
+                    << " is merge target, source " << children << dendl;
+           for (auto c : children) {
+             merge_pgs->insert(make_pair(c, q->first));
+              if (!did.count(c))
+                queue.push_back(c);
+           }
+           merge_pgs->insert(make_pair(cur, q->first));
+         }
        }
       }
-      new_pgs.insert(even_newer_pgs.begin(), even_newer_pgs.end());
-      curmap = nextmap;
-    }
-    assert(curmap == tomap); // we must have had both frommap and tomap
-  }
-}
-
-void OSDService::expand_pg_num(OSDMapRef old_map,
-                              OSDMapRef new_map)
-{
-  Mutex::Locker l(in_progress_split_lock);
-  for (set<spg_t>::iterator i = in_progress_splits.begin();
-       i != in_progress_splits.end();
-    ) {
-    if (!new_map->have_pg_pool(i->pool())) {
-      in_progress_splits.erase(i++);
-    } else {
-      _maybe_split_pgid(old_map, new_map, *i);
-      ++i;
-    }
-  }
-  for (map<spg_t, spg_t>::iterator i = pending_splits.begin();
-       i != pending_splits.end();
-    ) {
-    if (!new_map->have_pg_pool(i->first.pool())) {
-      rev_pending_splits.erase(i->second);
-      pending_splits.erase(i++);
-    } else {
-      _maybe_split_pgid(old_map, new_map, i->first);
-      ++i;
+      pgnum = q->second;
     }
   }
 }
 
-bool OSDService::splitting(spg_t pgid)
-{
-  Mutex::Locker l(in_progress_split_lock);
-  return in_progress_splits.count(pgid) ||
-    pending_splits.count(pgid);
-}
-
-void OSDService::complete_split(const set<spg_t> &pgs)
-{
-  Mutex::Locker l(in_progress_split_lock);
-  for (set<spg_t>::const_iterator i = pgs.begin();
-       i != pgs.end();
-       ++i) {
-    dout(10) << __func__ << ": Completing split on pg " << *i << dendl;
-    assert(!pending_splits.count(*i));
-    assert(in_progress_splits.count(*i));
-    in_progress_splits.erase(*i);
-  }
-}
-
 void OSDService::need_heartbeat_peer_update()
 {
   osd->need_heartbeat_peer_update();
 }
 
-void OSDService::pg_stat_queue_enqueue(PG *pg)
+HeartbeatStampsRef OSDService::get_hb_stamps(unsigned peer)
 {
-  osd->pg_stat_queue_enqueue(pg);
+  std::lock_guard l(hb_stamp_lock);
+  if (peer >= hb_stamps.size()) {
+    hb_stamps.resize(peer + 1);
+  }
+  if (!hb_stamps[peer]) {
+    hb_stamps[peer] = ceph::make_ref<HeartbeatStamps>(peer);
+  }
+  return hb_stamps[peer];
 }
 
-void OSDService::pg_stat_queue_dequeue(PG *pg)
+void OSDService::queue_renew_lease(epoch_t epoch, spg_t spgid)
 {
-  osd->pg_stat_queue_dequeue(pg);
+  osd->enqueue_peering_evt(
+    spgid,
+    PGPeeringEventRef(
+      std::make_shared<PGPeeringEvent>(
+       epoch, epoch,
+       RenewLease())));
 }
 
 void OSDService::start_shutdown()
 {
   {
-    Mutex::Locker l(agent_timer_lock);
+    std::lock_guard l(agent_timer_lock);
     agent_timer.shutdown();
   }
+
+  {
+    std::lock_guard l(sleep_lock);
+    sleep_timer.shutdown();
+  }
+
+  {
+    std::lock_guard l(recovery_request_lock);
+    recovery_request_timer.shutdown();
+  }
 }
 
-void OSDService::shutdown()
+void OSDService::shutdown_reserver()
 {
   reserver_finisher.wait_for_empty();
   reserver_finisher.stop();
-  {
-    Mutex::Locker l(watch_lock);
-    watch_timer.shutdown();
-  }
+}
 
-  objecter->shutdown();
-  objecter_finisher.wait_for_empty();
-  objecter_finisher.stop();
+void OSDService::shutdown()
+{
+  mono_timer.suspend();
 
   {
-    Mutex::Locker l(recovery_request_lock);
-    recovery_request_timer.shutdown();
+    std::lock_guard l(watch_lock);
+    watch_timer.shutdown();
   }
 
-  {
-    Mutex::Locker l(snap_sleep_lock);
-    snap_sleep_timer.shutdown();
+  objecter->shutdown();
+  for (auto& f : objecter_finishers) {
+    f->wait_for_empty();
+    f->stop();
   }
 
-  osdmap = OSDMapRef();
+  publish_map(OSDMapRef());
   next_osdmap = OSDMapRef();
 }
 
 void OSDService::init()
 {
   reserver_finisher.start();
-  objecter_finisher.start();
+  for (auto& f : objecter_finishers) {
+    f->start();
+  }
   objecter->set_client_incarnation(0);
 
   // deprioritize objecter in daemonperf output
@@ -518,7 +543,7 @@ void OSDService::init()
 
   watch_timer.init();
   agent_timer.init();
-  snap_sleep_timer.init();
+  mono_timer.resume();
 
   agent_thread.create("osd_srv_agent");
 
@@ -534,14 +559,19 @@ void OSDService::final_init()
 void OSDService::activate_map()
 {
   // wake/unwake the tiering agent
-  agent_lock.Lock();
+  std::lock_guard l{agent_lock};
   agent_active =
     !osdmap->test_flag(CEPH_OSDMAP_NOTIERAGENT) &&
     osd->is_active();
-  agent_cond.Signal();
-  agent_lock.Unlock();
+  agent_cond.notify_all();
+}
+
+void OSDService::request_osdmap_update(epoch_t e)
+{
+  osd->osdmap_subscribe(e, false);
 }
 
+
 class AgentTimeoutCB : public Context {
   PGRef pg;
 public:
@@ -554,12 +584,12 @@ public:
 void OSDService::agent_entry()
 {
   dout(10) << __func__ << " start" << dendl;
-  agent_lock.Lock();
+  std::unique_lock agent_locker{agent_lock};
 
   while (!agent_stop_flag) {
     if (agent_queue.empty()) {
       dout(20) << __func__ << " empty queue" << dendl;
-      agent_cond.Wait(agent_lock);
+      agent_cond.wait(agent_locker);
       continue;
     }
     uint64_t level = agent_queue.rbegin()->first;
@@ -578,7 +608,7 @@ void OSDService::agent_entry()
     if (!flush_mode_high_count)
       agent_flush_quota = cct->_conf->osd_agent_max_low_ops - agent_ops;
     if (agent_flush_quota <= 0 || top.empty() || !agent_active) {
-      agent_cond.Wait(agent_lock);
+      agent_cond.wait(agent_locker);
       continue;
     }
 
@@ -590,41 +620,39 @@ void OSDService::agent_entry()
     dout(10) << "high_count " << flush_mode_high_count
             << " agent_ops " << agent_ops
             << " flush_quota " << agent_flush_quota << dendl;
-    agent_lock.Unlock();
+    agent_locker.unlock();
     if (!pg->agent_work(max, agent_flush_quota)) {
-      dout(10) << __func__ << " " << pg->get_pgid()
+      dout(10) << __func__ << " " << pg->pg_id
        << " no agent_work, delay for " << cct->_conf->osd_agent_delay_time
        << " seconds" << dendl;
 
-      osd->logger->inc(l_osd_tier_delay);
+      logger->inc(l_osd_tier_delay);
       // Queue a timer to call agent_choose_mode for this pg in 5 seconds
-      agent_timer_lock.Lock();
+      std::lock_guard timer_locker{agent_timer_lock};
       Context *cb = new AgentTimeoutCB(pg);
       agent_timer.add_event_after(cct->_conf->osd_agent_delay_time, cb);
-      agent_timer_lock.Unlock();
     }
-    agent_lock.Lock();
+    agent_locker.lock();
   }
-  agent_lock.Unlock();
   dout(10) << __func__ << " finish" << dendl;
 }
 
 void OSDService::agent_stop()
 {
   {
-    Mutex::Locker l(agent_lock);
+    std::lock_guard l(agent_lock);
 
     // By this time all ops should be cancelled
-    assert(agent_ops == 0);
+    ceph_assert(agent_ops == 0);
     // By this time all PGs are shutdown and dequeued
     if (!agent_queue.empty()) {
       set<PGRef>& top = agent_queue.rbegin()->second;
-      derr << "agent queue not empty, for example " << (*top.begin())->info.pgid << dendl;
-      assert(0 == "agent queue not empty");
+      derr << "agent queue not empty, for example " << (*top.begin())->get_pgid() << dendl;
+      ceph_abort_msg("agent queue not empty");
     }
 
     agent_stop_flag = true;
-    agent_cond.Signal();
+    agent_cond.notify_all();
   }
   agent_thread.join();
 }
@@ -646,9 +674,9 @@ void OSDService::promote_throttle_recalibrate()
   uint64_t attempts, obj, bytes;
   promote_counter.sample_and_attenuate(&attempts, &obj, &bytes);
   dout(10) << __func__ << " " << attempts << " attempts, promoted "
-          << obj << " objects and " << pretty_si_t(bytes) << " bytes; target "
+          << obj << " objects and " << byte_u_t(bytes) << "; target "
           << target_obj_sec << " obj/sec or "
-          << pretty_si_t(target_bytes_sec) << " bytes/sec"
+          << byte_u_t(target_bytes_sec) << "/sec"
           << dendl;
 
   // calculate what the probability *should* be, given the targets
@@ -656,14 +684,14 @@ void OSDService::promote_throttle_recalibrate()
   if (attempts && dur > 0) {
     uint64_t avg_size = 1;
     if (obj)
-      avg_size = MAX(bytes / obj, 1);
+      avg_size = std::max<uint64_t>(bytes / obj, 1);
     unsigned po = (double)target_obj_sec * dur * 1000.0 / (double)attempts;
     unsigned pb = (double)target_bytes_sec / (double)avg_size * dur * 1000.0
       / (double)attempts;
     dout(20) << __func__ << "  po " << po << " pb " << pb << " avg_size "
             << avg_size << dendl;
     if (target_obj_sec && target_bytes_sec)
-      new_prob = MIN(po, pb);
+      new_prob = std::min(po, pb);
     else if (target_obj_sec)
       new_prob = po;
     else if (target_bytes_sec)
@@ -683,13 +711,13 @@ void OSDService::promote_throttle_recalibrate()
     ratio = (double)actual / (double)prob;
     new_prob = (double)new_prob / ratio;
   }
-  new_prob = MAX(new_prob, min_prob);
-  new_prob = MIN(new_prob, 1000);
+  new_prob = std::max(new_prob, min_prob);
+  new_prob = std::min(new_prob, 1000u);
 
   // adjust
   prob = (prob + new_prob) / 2;
-  prob = MAX(prob, min_prob);
-  prob = MIN(prob, 1000);
+  prob = std::max(prob, min_prob);
+  prob = std::min(prob, 1000u);
   dout(10) << __func__ << "  actual " << actual
           << ", actual/prob ratio " << ratio
           << ", adjusted new_prob " << new_prob
@@ -698,8 +726,8 @@ void OSDService::promote_throttle_recalibrate()
   promote_probability_millis = prob;
 
   // set hard limits for this interval to mitigate stampedes
-  promote_max_objects = target_obj_sec * OSD::OSD_TICK_INTERVAL * 2;
-  promote_max_bytes = target_bytes_sec * OSD::OSD_TICK_INTERVAL * 2;
+  promote_max_objects = target_obj_sec * osd->OSD_TICK_INTERVAL * 2;
+  promote_max_bytes = target_bytes_sec * osd->OSD_TICK_INTERVAL * 2;
 }
 
 // -------------------------------------
@@ -711,28 +739,22 @@ float OSDService::get_failsafe_full_ratio()
   return full_ratio;
 }
 
-void OSDService::check_full_status(const osd_stat_t &osd_stat)
+OSDService::s_names OSDService::recalc_full_state(float ratio, float pratio, string &inject)
 {
-  Mutex::Locker l(full_status_lock);
-
-  float ratio = ((float)osd_stat.kb_used) / ((float)osd_stat.kb);
-  cur_ratio = ratio;
-
   // The OSDMap ratios take precendence.  So if the failsafe is .95 and
   // the admin sets the cluster full to .96, the failsafe moves up to .96
   // too.  (Not that having failsafe == full is ideal, but it's better than
   // dropping writes before the clusters appears full.)
   OSDMapRef osdmap = get_osdmap();
   if (!osdmap || osdmap->get_epoch() == 0) {
-    cur_state = NONE;
-    return;
+    return NONE;
   }
   float nearfull_ratio = osdmap->get_nearfull_ratio();
   float backfillfull_ratio = std::max(osdmap->get_backfillfull_ratio(), nearfull_ratio);
   float full_ratio = std::max(osdmap->get_full_ratio(), backfillfull_ratio);
   float failsafe_ratio = std::max(get_failsafe_full_ratio(), full_ratio);
 
-  if (!osdmap->test_flag(CEPH_OSDMAP_REQUIRE_LUMINOUS)) {
+  if (osdmap->require_osd_release < ceph_release_t::luminous) {
     // use the failsafe for nearfull and full; the mon isn't using the
     // flags anyway because we're mid-upgrade.
     full_ratio = failsafe_ratio;
@@ -749,27 +771,34 @@ void OSDService::check_full_status(const osd_stat_t &osd_stat)
     nearfull_ratio = failsafe_ratio;
   }
 
-  string inject;
-  s_names new_state;
   if (injectfull_state > NONE && injectfull) {
-    new_state = injectfull_state;
     inject = "(Injected)";
-  } else if (ratio > failsafe_ratio) {
-    new_state = FAILSAFE;
+    return injectfull_state;
+  } else if (pratio > failsafe_ratio) {
+    return FAILSAFE;
   } else if (ratio > full_ratio) {
-    new_state = FULL;
+    return FULL;
   } else if (ratio > backfillfull_ratio) {
-    new_state = BACKFILLFULL;
-  } else if (ratio > nearfull_ratio) {
-    new_state = NEARFULL;
-  } else {
-    new_state = NONE;
+    return BACKFILLFULL;
+  } else if (pratio > nearfull_ratio) {
+    return NEARFULL;
   }
+   return NONE;
+}
+
+void OSDService::check_full_status(float ratio, float pratio)
+{
+  std::lock_guard l(full_status_lock);
+
+  cur_ratio = ratio;
+  physical_ratio = pratio;
+
+  string inject;
+  s_names new_state;
+  new_state = recalc_full_state(ratio, pratio, inject);
+
   dout(20) << __func__ << " cur ratio " << ratio
-          << ". nearfull_ratio " << nearfull_ratio
-          << ". backfillfull_ratio " << backfillfull_ratio
-          << ", full_ratio " << full_ratio
-          << ", failsafe_ratio " << failsafe_ratio
+           << ", physical ratio " << pratio
           << ", new state " << get_full_state_name(new_state)
           << " " << inject
           << dendl;
@@ -779,11 +808,11 @@ void OSDService::check_full_status(const osd_stat_t &osd_stat)
     dout(10) << __func__ << " " << get_full_state_name(cur_state)
             << " -> " << get_full_state_name(new_state) << dendl;
     if (new_state == FAILSAFE) {
-      clog->error() << "failsafe engaged, dropping updates, now "
+      clog->error() << "full status failsafe engaged, dropping updates, now "
                    << (int)roundf(ratio * 100) << "% full";
     } else if (cur_state == FAILSAFE) {
-      clog->error() << "failsafe disengaged, no longer dropping updates, now "
-                   << (int)roundf(ratio * 100) << "% full";
+      clog->error() << "full status failsafe disengaged, no longer dropping "
+                    << "updates, now " << (int)roundf(ratio * 100) << "% full";
     }
     cur_state = new_state;
   }
@@ -812,123 +841,220 @@ bool OSDService::need_fullness_update()
   return want != cur;
 }
 
-bool OSDService::_check_full(s_names type, ostream &ss) const
+bool OSDService::_check_inject_full(DoutPrefixProvider *dpp, s_names type) const
 {
-  Mutex::Locker l(full_status_lock);
-
   if (injectfull && injectfull_state >= type) {
     // injectfull is either a count of the number of times to return failsafe full
     // or if -1 then always return full
     if (injectfull > 0)
       --injectfull;
-    ss << "Injected " << get_full_state_name(type) << " OSD ("
-       << (injectfull < 0 ? "set" : std::to_string(injectfull)) << ")";
+    ldpp_dout(dpp, 10) << __func__ << " Injected " << get_full_state_name(type) << " OSD ("
+             << (injectfull < 0 ? "set" : std::to_string(injectfull)) << ")"
+             << dendl;
     return true;
   }
+  return false;
+}
+
+bool OSDService::_check_full(DoutPrefixProvider *dpp, s_names type) const
+{
+  std::lock_guard l(full_status_lock);
+
+  if (_check_inject_full(dpp, type))
+    return true;
+
+  if (cur_state >= type)
+    ldpp_dout(dpp, 10) << __func__ << " current usage is " << cur_ratio
+                       << " physical " << physical_ratio << dendl;
 
-  ss << "current usage is " << cur_ratio;
   return cur_state >= type;
 }
 
-bool OSDService::check_failsafe_full(ostream &ss) const
+bool OSDService::_tentative_full(DoutPrefixProvider *dpp, s_names type, uint64_t adjust_used, osd_stat_t adjusted_stat)
+{
+  ldpp_dout(dpp, 20) << __func__ << " type " << get_full_state_name(type) << " adjust_used " << (adjust_used >> 10) << "KiB" << dendl;
+  {
+    std::lock_guard l(full_status_lock);
+    if (_check_inject_full(dpp, type)) {
+      return true;
+    }
+  }
+
+  float pratio;
+  float ratio = compute_adjusted_ratio(adjusted_stat, &pratio, adjust_used);
+
+  string notused;
+  s_names tentative_state = recalc_full_state(ratio, pratio, notused);
+
+  if (tentative_state >= type)
+    ldpp_dout(dpp, 10) << __func__ << " tentative usage is " << ratio << dendl;
+
+  return tentative_state >= type;
+}
+
+bool OSDService::check_failsafe_full(DoutPrefixProvider *dpp) const
+{
+  return _check_full(dpp, FAILSAFE);
+}
+
+bool OSDService::check_full(DoutPrefixProvider *dpp) const
 {
-  return _check_full(FAILSAFE, ss);
+  return _check_full(dpp, FULL);
 }
 
-bool OSDService::check_full(ostream &ss) const
+bool OSDService::tentative_backfill_full(DoutPrefixProvider *dpp, uint64_t adjust_used, osd_stat_t stats)
 {
-  return _check_full(FULL, ss);
+  return _tentative_full(dpp, BACKFILLFULL, adjust_used, stats);
 }
 
-bool OSDService::check_backfill_full(ostream &ss) const
+bool OSDService::check_backfill_full(DoutPrefixProvider *dpp) const
 {
-  return _check_full(BACKFILLFULL, ss);
+  return _check_full(dpp, BACKFILLFULL);
 }
 
-bool OSDService::check_nearfull(ostream &ss) const
+bool OSDService::check_nearfull(DoutPrefixProvider *dpp) const
 {
-  return _check_full(NEARFULL, ss);
+  return _check_full(dpp, NEARFULL);
 }
 
 bool OSDService::is_failsafe_full() const
 {
-  Mutex::Locker l(full_status_lock);
+  std::lock_guard l(full_status_lock);
   return cur_state == FAILSAFE;
 }
 
 bool OSDService::is_full() const
 {
-  Mutex::Locker l(full_status_lock);
+  std::lock_guard l(full_status_lock);
   return cur_state >= FULL;
 }
 
 bool OSDService::is_backfillfull() const
 {
-  Mutex::Locker l(full_status_lock);
+  std::lock_guard l(full_status_lock);
   return cur_state >= BACKFILLFULL;
 }
 
 bool OSDService::is_nearfull() const
 {
-  Mutex::Locker l(full_status_lock);
+  std::lock_guard l(full_status_lock);
   return cur_state >= NEARFULL;
 }
 
 void OSDService::set_injectfull(s_names type, int64_t count)
 {
-  Mutex::Locker l(full_status_lock);
+  std::lock_guard l(full_status_lock);
   injectfull_state = type;
   injectfull = count;
 }
 
-void OSDService::update_osd_stat(vector<int>& hb_peers)
+void OSDService::set_statfs(const struct store_statfs_t &stbuf,
+                           osd_alert_list_t& alerts)
 {
-  Mutex::Locker lock(stat_lock);
-
-  osd_stat.hb_peers.swap(hb_peers);
-
-  osd->op_tracker.get_age_ms_histogram(&osd_stat.op_queue_age_hist);
-
-  // fill in osd stats too
-  struct store_statfs_t stbuf;
-  int r = osd->store->statfs(&stbuf);
-  if (r < 0) {
-    derr << "statfs() failed: " << cpp_strerror(r) << dendl;
-    return;
-  }
-
   uint64_t bytes = stbuf.total;
-  uint64_t used = bytes - stbuf.available;
   uint64_t avail = stbuf.available;
+  uint64_t used = stbuf.get_used_raw();
+
+  // For testing fake statfs values so it doesn't matter if all
+  // OSDs are using the same partition.
+  if (cct->_conf->fake_statfs_for_testing) {
+    uint64_t total_num_bytes = 0;
+    vector<PGRef> pgs;
+    osd->_get_pgs(&pgs);
+    for (auto p : pgs) {
+      total_num_bytes += p->get_stats_num_bytes();
+    }
+    bytes = cct->_conf->fake_statfs_for_testing;
+    if (total_num_bytes < bytes)
+      avail = bytes - total_num_bytes;
+    else
+      avail = 0;
+    dout(0) << __func__ << " fake total " << cct->_conf->fake_statfs_for_testing
+            << " adjust available " << avail
+            << dendl;
+    used = bytes - avail;
+  }
 
-  osd_stat.kb = bytes >> 10;
-  osd_stat.kb_used = used >> 10;
-  osd_stat.kb_avail = avail >> 10;
+  logger->set(l_osd_stat_bytes, bytes);
+  logger->set(l_osd_stat_bytes_used, used);
+  logger->set(l_osd_stat_bytes_avail, avail);
 
-  osd->logger->set(l_osd_stat_bytes, bytes);
-  osd->logger->set(l_osd_stat_bytes_used, used);
-  osd->logger->set(l_osd_stat_bytes_avail, avail);
+  std::lock_guard l(stat_lock);
+  osd_stat.statfs = stbuf;
+  osd_stat.os_alerts.clear();
+  osd_stat.os_alerts[whoami].swap(alerts);
+  if (cct->_conf->fake_statfs_for_testing) {
+    osd_stat.statfs.total = bytes;
+    osd_stat.statfs.available = avail;
+    // For testing don't want used to go negative, so clear reserved
+    osd_stat.statfs.internally_reserved = 0;
+  }
+}
 
-  dout(20) << "update_osd_stat " << osd_stat << dendl;
+osd_stat_t OSDService::set_osd_stat(vector<int>& hb_peers,
+                                   int num_pgs)
+{
+  utime_t now = ceph_clock_now();
+  auto stale_time = g_conf().get_val<int64_t>("osd_mon_heartbeat_stat_stale");
+  std::lock_guard l(stat_lock);
+  osd_stat.hb_peers.swap(hb_peers);
+  osd->op_tracker.get_age_ms_histogram(&osd_stat.op_queue_age_hist);
+  osd_stat.num_pgs = num_pgs;
+  // Clean entries that aren't updated
+  // This is called often enough that we can just remove 1 at a time
+  for (auto i: osd_stat.hb_pingtime) {
+    if (i.second.last_update == 0)
+      continue;
+    if (stale_time && now.sec() - i.second.last_update > stale_time) {
+      dout(20) << __func__ << " time out heartbeat for osd " << i.first
+              << " last_update " << i.second.last_update << dendl;
+      osd_stat.hb_pingtime.erase(i.first);
+      break;
+    }
+  }
+  return osd_stat;
+}
 
-  check_full_status(osd_stat);
+void OSDService::inc_osd_stat_repaired()
+{
+  std::lock_guard l(stat_lock);
+  osd_stat.num_shards_repaired++;
+  return;
 }
 
-bool OSDService::check_osdmap_full(const set<pg_shard_t> &missing_on)
+float OSDService::compute_adjusted_ratio(osd_stat_t new_stat, float *pratio,
+                                        uint64_t adjust_used)
 {
-  OSDMapRef osdmap = get_osdmap();
-  for (auto shard : missing_on) {
-    if (osdmap->get_state(shard.osd) & CEPH_OSD_FULL)
-      return true;
+  *pratio =
+   ((float)new_stat.statfs.get_used_raw()) / ((float)new_stat.statfs.total);
+
+  if (adjust_used) {
+    dout(20) << __func__ << " Before kb_used() " << new_stat.statfs.kb_used()  << dendl;
+    if (new_stat.statfs.available > adjust_used)
+      new_stat.statfs.available -= adjust_used;
+    else
+      new_stat.statfs.available = 0;
+    dout(20) << __func__ << " After kb_used() " << new_stat.statfs.kb_used() << dendl;
   }
-  return false;
+
+  // Check all pgs and adjust kb_used to include all pending backfill data
+  int backfill_adjusted = 0;
+  vector<PGRef> pgs;
+  osd->_get_pgs(&pgs);
+  for (auto p : pgs) {
+    backfill_adjusted += p->pg_stat_adjust(&new_stat);
+  }
+  if (backfill_adjusted) {
+    dout(20) << __func__ << " backfill adjusted " << new_stat << dendl;
+  }
+  return ((float)new_stat.statfs.get_used_raw()) / ((float)new_stat.statfs.total);
 }
 
 void OSDService::send_message_osd_cluster(int peer, Message *m, epoch_t from_epoch)
 {
   OSDMapRef next_map = get_nextmap_reserved();
   // service map is always newer/newest
-  assert(from_epoch <= next_map->get_epoch());
+  ceph_assert(from_epoch <= next_map->get_epoch());
 
   if (next_map->is_down(peer) ||
       next_map->get_info(peer).up_from > from_epoch) {
@@ -936,25 +1062,60 @@ void OSDService::send_message_osd_cluster(int peer, Message *m, epoch_t from_epo
     release_map(next_map);
     return;
   }
-  const entity_inst_t& peer_inst = next_map->get_cluster_inst(peer);
-  ConnectionRef peer_con = osd->cluster_messenger->get_connection(peer_inst);
-  share_map_peer(peer, peer_con.get(), next_map);
+  ConnectionRef peer_con;
+  if (peer == whoami) {
+    peer_con = osd->cluster_messenger->get_loopback_connection();
+  } else {
+    peer_con = osd->cluster_messenger->connect_to_osd(
+       next_map->get_cluster_addrs(peer), false, true);
+  }
+  maybe_share_map(peer_con.get(), next_map);
   peer_con->send_message(m);
   release_map(next_map);
 }
 
+void OSDService::send_message_osd_cluster(std::vector<std::pair<int, Message*>>& messages, epoch_t from_epoch)
+{
+  OSDMapRef next_map = get_nextmap_reserved();
+  // service map is always newer/newest
+  ceph_assert(from_epoch <= next_map->get_epoch());
+
+  for (auto& iter : messages) {
+    if (next_map->is_down(iter.first) ||
+       next_map->get_info(iter.first).up_from > from_epoch) {
+      iter.second->put();
+      continue;
+    }
+    ConnectionRef peer_con;
+    if (iter.first == whoami) {
+      peer_con = osd->cluster_messenger->get_loopback_connection();
+    } else {
+      peer_con = osd->cluster_messenger->connect_to_osd(
+         next_map->get_cluster_addrs(iter.first), false, true);
+    }
+    maybe_share_map(peer_con.get(), next_map);
+    peer_con->send_message(iter.second);
+  }
+  release_map(next_map);
+}
 ConnectionRef OSDService::get_con_osd_cluster(int peer, epoch_t from_epoch)
 {
   OSDMapRef next_map = get_nextmap_reserved();
   // service map is always newer/newest
-  assert(from_epoch <= next_map->get_epoch());
+  ceph_assert(from_epoch <= next_map->get_epoch());
 
   if (next_map->is_down(peer) ||
       next_map->get_info(peer).up_from > from_epoch) {
     release_map(next_map);
     return NULL;
   }
-  ConnectionRef con = osd->cluster_messenger->get_connection(next_map->get_cluster_inst(peer));
+  ConnectionRef con;
+  if (peer == whoami) {
+    con = osd->cluster_messenger->get_loopback_connection();
+  } else {
+    con = osd->cluster_messenger->connect_to_osd(
+       next_map->get_cluster_addrs(peer), false, true);
+  }
   release_map(next_map);
   return con;
 }
@@ -963,7 +1124,7 @@ pair<ConnectionRef,ConnectionRef> OSDService::get_con_osd_hb(int peer, epoch_t f
 {
   OSDMapRef next_map = get_nextmap_reserved();
   // service map is always newer/newest
-  assert(from_epoch <= next_map->get_epoch());
+  ceph_assert(from_epoch <= next_map->get_epoch());
 
   pair<ConnectionRef,ConnectionRef> ret;
   if (next_map->is_down(peer) ||
@@ -971,43 +1132,53 @@ pair<ConnectionRef,ConnectionRef> OSDService::get_con_osd_hb(int peer, epoch_t f
     release_map(next_map);
     return ret;
   }
-  ret.first = osd->hb_back_client_messenger->get_connection(next_map->get_hb_back_inst(peer));
-  if (next_map->get_hb_front_addr(peer) != entity_addr_t())
-    ret.second = osd->hb_front_client_messenger->get_connection(next_map->get_hb_front_inst(peer));
+  ret.first = osd->hb_back_client_messenger->connect_to_osd(
+    next_map->get_hb_back_addrs(peer));
+  ret.second = osd->hb_front_client_messenger->connect_to_osd(
+    next_map->get_hb_front_addrs(peer));
   release_map(next_map);
   return ret;
 }
 
+entity_name_t OSDService::get_cluster_msgr_name() const
+{
+  return cluster_messenger->get_myname();
+}
 
-void OSDService::queue_want_pg_temp(pg_t pgid, vector<int>& want)
+void OSDService::queue_want_pg_temp(pg_t pgid,
+                                   const vector<int>& want,
+                                   bool forced)
 {
-  Mutex::Locker l(pg_temp_lock);
-  map<pg_t,vector<int> >::iterator p = pg_temp_pending.find(pgid);
+  std::lock_guard l(pg_temp_lock);
+  auto p = pg_temp_pending.find(pgid);
   if (p == pg_temp_pending.end() ||
-      p->second != want) {
-    pg_temp_wanted[pgid] = want;
+      p->second.acting != want ||
+      forced) {
+    pg_temp_wanted[pgid] = {want, forced};
   }
 }
 
 void OSDService::remove_want_pg_temp(pg_t pgid)
 {
-  Mutex::Locker l(pg_temp_lock);
+  std::lock_guard l(pg_temp_lock);
   pg_temp_wanted.erase(pgid);
   pg_temp_pending.erase(pgid);
 }
 
 void OSDService::_sent_pg_temp()
 {
-  for (map<pg_t,vector<int> >::iterator p = pg_temp_wanted.begin();
-       p != pg_temp_wanted.end();
-       ++p)
-    pg_temp_pending[p->first] = p->second;
+#ifdef HAVE_STDLIB_MAP_SPLICING
+  pg_temp_pending.merge(pg_temp_wanted);
+#else
+  pg_temp_pending.insert(make_move_iterator(begin(pg_temp_wanted)),
+                        make_move_iterator(end(pg_temp_wanted)));
+#endif
   pg_temp_wanted.clear();
 }
 
 void OSDService::requeue_pg_temp()
 {
-  Mutex::Locker l(pg_temp_lock);
+  std::lock_guard l(pg_temp_lock);
   // wanted overrides pending.  note that remove_want_pg_temp
   // clears the item out of both.
   unsigned old_wanted = pg_temp_wanted.size();
@@ -1018,250 +1189,161 @@ void OSDService::requeue_pg_temp()
           << pg_temp_wanted.size() << dendl;
 }
 
+std::ostream& operator<<(std::ostream& out,
+                        const OSDService::pg_temp_t& pg_temp)
+{
+  out << pg_temp.acting;
+  if (pg_temp.forced) {
+    out << " (forced)";
+  }
+  return out;
+}
+
 void OSDService::send_pg_temp()
 {
-  Mutex::Locker l(pg_temp_lock);
+  std::lock_guard l(pg_temp_lock);
   if (pg_temp_wanted.empty())
     return;
   dout(10) << "send_pg_temp " << pg_temp_wanted << dendl;
-  MOSDPGTemp *m = new MOSDPGTemp(osdmap->get_epoch());
-  m->pg_temp = pg_temp_wanted;
-  monc->send_mon_message(m);
+  MOSDPGTemp *ms[2] = {nullptr, nullptr};
+  for (auto& [pgid, pg_temp] : pg_temp_wanted) {
+    auto& m = ms[pg_temp.forced];
+    if (!m) {
+      m = new MOSDPGTemp(osdmap->get_epoch());
+      m->forced = pg_temp.forced;
+    }
+    m->pg_temp.emplace(pgid, pg_temp.acting);
+  }
+  for (auto m : ms) {
+    if (m) {
+      monc->send_mon_message(m);
+    }
+  }
   _sent_pg_temp();
 }
 
 void OSDService::send_pg_created(pg_t pgid)
 {
+  std::lock_guard l(pg_created_lock);
   dout(20) << __func__ << dendl;
-  monc->send_mon_message(new MOSDPGCreated(pgid));
-}
-
-// --------------------------------------
-// dispatch
-
-epoch_t OSDService::get_peer_epoch(int peer)
-{
-  Mutex::Locker l(peer_map_epoch_lock);
-  map<int,epoch_t>::iterator p = peer_map_epoch.find(peer);
-  if (p == peer_map_epoch.end())
-    return 0;
-  return p->second;
-}
-
-epoch_t OSDService::note_peer_epoch(int peer, epoch_t e)
-{
-  Mutex::Locker l(peer_map_epoch_lock);
-  map<int,epoch_t>::iterator p = peer_map_epoch.find(peer);
-  if (p != peer_map_epoch.end()) {
-    if (p->second < e) {
-      dout(10) << "note_peer_epoch osd." << peer << " has " << e << dendl;
-      p->second = e;
-    } else {
-      dout(30) << "note_peer_epoch osd." << peer << " has " << p->second << " >= " << e << dendl;
-    }
-    return p->second;
-  } else {
-    dout(10) << "note_peer_epoch osd." << peer << " now has " << e << dendl;
-    peer_map_epoch[peer] = e;
-    return e;
-  }
-}
-
-void OSDService::forget_peer_epoch(int peer, epoch_t as_of)
-{
-  Mutex::Locker l(peer_map_epoch_lock);
-  map<int,epoch_t>::iterator p = peer_map_epoch.find(peer);
-  if (p != peer_map_epoch.end()) {
-    if (p->second <= as_of) {
-      dout(10) << "forget_peer_epoch osd." << peer << " as_of " << as_of
-              << " had " << p->second << dendl;
-      peer_map_epoch.erase(p);
-    } else {
-      dout(10) << "forget_peer_epoch osd." << peer << " as_of " << as_of
-              << " has " << p->second << " - not forgetting" << dendl;
-    }
+  auto o = get_osdmap();
+  if (o->require_osd_release >= ceph_release_t::luminous) {
+    pg_created.insert(pgid);
+    monc->send_mon_message(new MOSDPGCreated(pgid));
   }
 }
 
-bool OSDService::should_share_map(entity_name_t name, Connection *con,
-                                  epoch_t epoch, const OSDMapRef& osdmap,
-                                  const epoch_t *sent_epoch_p)
+void OSDService::send_pg_created()
 {
-  dout(20) << "should_share_map "
-           << name << " " << con->get_peer_addr()
-           << " " << epoch << dendl;
-
-  // does client have old map?
-  if (name.is_client()) {
-    bool message_sendmap = epoch < osdmap->get_epoch();
-    if (message_sendmap && sent_epoch_p) {
-      dout(20) << "client session last_sent_epoch: "
-               << *sent_epoch_p
-               << " versus osdmap epoch " << osdmap->get_epoch() << dendl;
-      if (*sent_epoch_p < osdmap->get_epoch()) {
-        return true;
-      } // else we don't need to send it out again
-    }
-  }
-
-  if (con->get_messenger() == osd->cluster_messenger &&
-      con != osd->cluster_messenger->get_loopback_connection() &&
-      osdmap->is_up(name.num()) &&
-      (osdmap->get_cluster_addr(name.num()) == con->get_peer_addr() ||
-       osdmap->get_hb_back_addr(name.num()) == con->get_peer_addr())) {
-    // remember
-    epoch_t has = MAX(get_peer_epoch(name.num()), epoch);
-
-    // share?
-    if (has < osdmap->get_epoch()) {
-      dout(10) << name << " " << con->get_peer_addr()
-               << " has old map " << epoch << " < "
-               << osdmap->get_epoch() << dendl;
-      return true;
+  std::lock_guard l(pg_created_lock);
+  dout(20) << __func__ << dendl;
+  auto o = get_osdmap();
+  if (o->require_osd_release >= ceph_release_t::luminous) {
+    for (auto pgid : pg_created) {
+      monc->send_mon_message(new MOSDPGCreated(pgid));
     }
   }
-
-  return false;
 }
 
-void OSDService::share_map(
-    entity_name_t name,
-    Connection *con,
-    epoch_t epoch,
-    OSDMapRef& osdmap,
-    epoch_t *sent_epoch_p)
+void OSDService::prune_pg_created()
 {
-  dout(20) << "share_map "
-          << name << " " << con->get_peer_addr()
-          << " " << epoch << dendl;
-
-  if (!osd->is_active()) {
-    /*It is safe not to proceed as OSD is not in healthy state*/
-    return;
-  }
-
-  bool want_shared = should_share_map(name, con, epoch,
-                                      osdmap, sent_epoch_p);
-
-  if (want_shared){
-    if (name.is_client()) {
-      dout(10) << name << " has old map " << epoch
-          << " < " << osdmap->get_epoch() << dendl;
-      // we know the Session is valid or we wouldn't be sending
-      if (sent_epoch_p) {
-       *sent_epoch_p = osdmap->get_epoch();
-      }
-      send_incremental_map(epoch, con, osdmap);
-    } else if (con->get_messenger() == osd->cluster_messenger &&
-        osdmap->is_up(name.num()) &&
-        (osdmap->get_cluster_addr(name.num()) == con->get_peer_addr() ||
-            osdmap->get_hb_back_addr(name.num()) == con->get_peer_addr())) {
-      dout(10) << name << " " << con->get_peer_addr()
-                      << " has old map " << epoch << " < "
-                      << osdmap->get_epoch() << dendl;
-      note_peer_epoch(name.num(), osdmap->get_epoch());
-      send_incremental_map(epoch, con, osdmap);
+  std::lock_guard l(pg_created_lock);
+  dout(20) << __func__ << dendl;
+  auto o = get_osdmap();
+  auto i = pg_created.begin();
+  while (i != pg_created.end()) {
+    auto p = o->get_pg_pool(i->pool());
+    if (!p || !p->has_flag(pg_pool_t::FLAG_CREATING)) {
+      dout(20) << __func__ << " pruning " << *i << dendl;
+      i = pg_created.erase(i);
+    } else {
+      dout(20) << __func__ << " keeping " << *i << dendl;
+      ++i;
     }
   }
 }
 
-void OSDService::share_map_peer(int peer, Connection *con, OSDMapRef map)
-{
-  if (!map)
-    map = get_osdmap();
 
-  // send map?
-  epoch_t pe = get_peer_epoch(peer);
-  if (pe) {
-    if (pe < map->get_epoch()) {
-      send_incremental_map(pe, con, map);
-      note_peer_epoch(peer, map->get_epoch());
-    } else
-      dout(20) << "share_map_peer " << con << " already has epoch " << pe << dendl;
-  } else {
-    dout(20) << "share_map_peer " << con << " don't know epoch, doing nothing" << dendl;
-    // no idea about peer's epoch.
-    // ??? send recent ???
-    // do nothing.
-  }
-}
+// --------------------------------------
+// dispatch
 
-bool OSDService::can_inc_scrubs_pending()
+bool OSDService::can_inc_scrubs()
 {
   bool can_inc = false;
-  Mutex::Locker l(sched_scrub_lock);
+  std::lock_guard l(sched_scrub_lock);
 
-  if (scrubs_pending + scrubs_active < cct->_conf->osd_max_scrubs) {
-    dout(20) << __func__ << " " << scrubs_pending << " -> " << (scrubs_pending+1)
-            << " (max " << cct->_conf->osd_max_scrubs << ", active " << scrubs_active << ")" << dendl;
+  if (scrubs_local + scrubs_remote < cct->_conf->osd_max_scrubs) {
+    dout(20) << __func__ << " == true " << scrubs_local << " local + " << scrubs_remote
+            << " remote < max " << cct->_conf->osd_max_scrubs << dendl;
     can_inc = true;
   } else {
-    dout(20) << __func__ << scrubs_pending << " + " << scrubs_active << " active >= max " << cct->_conf->osd_max_scrubs << dendl;
+    dout(20) << __func__ << " == false " << scrubs_local << " local + " << scrubs_remote
+            << " remote >= max " << cct->_conf->osd_max_scrubs << dendl;
   }
 
   return can_inc;
 }
 
-bool OSDService::inc_scrubs_pending()
+bool OSDService::inc_scrubs_local()
 {
   bool result = false;
-
-  sched_scrub_lock.Lock();
-  if (scrubs_pending + scrubs_active < cct->_conf->osd_max_scrubs) {
-    dout(20) << "inc_scrubs_pending " << scrubs_pending << " -> " << (scrubs_pending+1)
-            << " (max " << cct->_conf->osd_max_scrubs << ", active " << scrubs_active << ")" << dendl;
+  std::lock_guard l{sched_scrub_lock};
+  if (scrubs_local + scrubs_remote < cct->_conf->osd_max_scrubs) {
+    dout(20) << __func__ << " " << scrubs_local << " -> " << (scrubs_local+1)
+            << " (max " << cct->_conf->osd_max_scrubs << ", remote " << scrubs_remote << ")" << dendl;
     result = true;
-    ++scrubs_pending;
+    ++scrubs_local;
   } else {
-    dout(20) << "inc_scrubs_pending " << scrubs_pending << " + " << scrubs_active << " active >= max " << cct->_conf->osd_max_scrubs << dendl;
+    dout(20) << __func__ << " " << scrubs_local << " local + " << scrubs_remote << " remote >= max " << cct->_conf->osd_max_scrubs << dendl;
   }
-  sched_scrub_lock.Unlock();
-
   return result;
 }
 
-void OSDService::dec_scrubs_pending()
+void OSDService::dec_scrubs_local()
 {
-  sched_scrub_lock.Lock();
-  dout(20) << "dec_scrubs_pending " << scrubs_pending << " -> " << (scrubs_pending-1)
-          << " (max " << cct->_conf->osd_max_scrubs << ", active " << scrubs_active << ")" << dendl;
-  --scrubs_pending;
-  assert(scrubs_pending >= 0);
-  sched_scrub_lock.Unlock();
+  std::lock_guard l{sched_scrub_lock};
+  dout(20) << __func__ << " " << scrubs_local << " -> " << (scrubs_local-1)
+          << " (max " << cct->_conf->osd_max_scrubs << ", remote " << scrubs_remote << ")" << dendl;
+  --scrubs_local;
+  ceph_assert(scrubs_local >= 0);
 }
 
-void OSDService::inc_scrubs_active(bool reserved)
+bool OSDService::inc_scrubs_remote()
 {
-  sched_scrub_lock.Lock();
-  ++(scrubs_active);
-  if (reserved) {
-    --(scrubs_pending);
-    dout(20) << "inc_scrubs_active " << (scrubs_active-1) << " -> " << scrubs_active
-            << " (max " << cct->_conf->osd_max_scrubs
-            << ", pending " << (scrubs_pending+1) << " -> " << scrubs_pending << ")" << dendl;
-    assert(scrubs_pending >= 0);
+  bool result = false;
+  std::lock_guard l{sched_scrub_lock};
+  if (scrubs_local + scrubs_remote < cct->_conf->osd_max_scrubs) {
+    dout(20) << __func__ << " " << scrubs_remote << " -> " << (scrubs_remote+1)
+            << " (max " << cct->_conf->osd_max_scrubs << ", local " << scrubs_local << ")" << dendl;
+    result = true;
+    ++scrubs_remote;
   } else {
-    dout(20) << "inc_scrubs_active " << (scrubs_active-1) << " -> " << scrubs_active
-            << " (max " << cct->_conf->osd_max_scrubs
-            << ", pending " << scrubs_pending << ")" << dendl;
+    dout(20) << __func__ << " " << scrubs_local << " local + " << scrubs_remote << " remote >= max " << cct->_conf->osd_max_scrubs << dendl;
   }
-  sched_scrub_lock.Unlock();
+  return result;
+}
+
+void OSDService::dec_scrubs_remote()
+{
+  std::lock_guard l{sched_scrub_lock};
+  dout(20) << __func__ << " " << scrubs_remote << " -> " << (scrubs_remote-1)
+          << " (max " << cct->_conf->osd_max_scrubs << ", local " << scrubs_local << ")" << dendl;
+  --scrubs_remote;
+  ceph_assert(scrubs_remote >= 0);
 }
 
-void OSDService::dec_scrubs_active()
+void OSDService::dump_scrub_reservations(Formatter *f)
 {
-  sched_scrub_lock.Lock();
-  dout(20) << "dec_scrubs_active " << scrubs_active << " -> " << (scrubs_active-1)
-          << " (max " << cct->_conf->osd_max_scrubs << ", pending " << scrubs_pending << ")" << dendl;
-  --scrubs_active;
-  assert(scrubs_active >= 0);
-  sched_scrub_lock.Unlock();
+  std::lock_guard l{sched_scrub_lock};
+  f->dump_int("scrubs_local", scrubs_local);
+  f->dump_int("scrubs_remote", scrubs_remote);
+  f->dump_int("osd_max_scrubs", cct->_conf->osd_max_scrubs);
 }
 
 void OSDService::retrieve_epochs(epoch_t *_boot_epoch, epoch_t *_up_epoch,
                                  epoch_t *_bind_epoch) const
 {
-  Mutex::Locker l(epoch_lock);
+  std::lock_guard l(epoch_lock);
   if (_boot_epoch)
     *_boot_epoch = boot_epoch;
   if (_up_epoch)
@@ -1273,24 +1355,24 @@ void OSDService::retrieve_epochs(epoch_t *_boot_epoch, epoch_t *_up_epoch,
 void OSDService::set_epochs(const epoch_t *_boot_epoch, const epoch_t *_up_epoch,
                             const epoch_t *_bind_epoch)
 {
-  Mutex::Locker l(epoch_lock);
+  std::lock_guard l(epoch_lock);
   if (_boot_epoch) {
-    assert(*_boot_epoch == 0 || *_boot_epoch >= boot_epoch);
+    ceph_assert(*_boot_epoch == 0 || *_boot_epoch >= boot_epoch);
     boot_epoch = *_boot_epoch;
   }
   if (_up_epoch) {
-    assert(*_up_epoch == 0 || *_up_epoch >= up_epoch);
+    ceph_assert(*_up_epoch == 0 || *_up_epoch >= up_epoch);
     up_epoch = *_up_epoch;
   }
   if (_bind_epoch) {
-    assert(*_bind_epoch == 0 || *_bind_epoch >= bind_epoch);
+    ceph_assert(*_bind_epoch == 0 || *_bind_epoch >= bind_epoch);
     bind_epoch = *_bind_epoch;
   }
 }
 
 bool OSDService::prepare_to_stop()
 {
-  Mutex::Locker l(is_stopping_lock);
+  std::unique_lock l(is_stopping_lock);
   if (get_state() != NOT_STOPPING)
     return false;
 
@@ -1298,18 +1380,17 @@ bool OSDService::prepare_to_stop()
   if (osdmap && osdmap->is_up(whoami)) {
     dout(0) << __func__ << " telling mon we are shutting down" << dendl;
     set_state(PREPARING_TO_STOP);
-    monc->send_mon_message(new MOSDMarkMeDown(monc->get_fsid(),
-                                             osdmap->get_inst(whoami),
-                                             osdmap->get_epoch(),
-                                             true  // request ack
-                                             ));
-    utime_t now = ceph_clock_now();
-    utime_t timeout;
-    timeout.set_from_double(now + cct->_conf->osd_mon_shutdown_timeout);
-    while ((ceph_clock_now() < timeout) &&
-       (get_state() != STOPPING)) {
-      is_stopping_cond.WaitUntil(is_stopping_lock, timeout);
-    }
+    monc->send_mon_message(
+      new MOSDMarkMeDown(
+       monc->get_fsid(),
+       whoami,
+       osdmap->get_addrs(whoami),
+       osdmap->get_epoch(),
+       true  // request ack
+       ));
+    const auto timeout = ceph::make_timespan(cct->_conf->osd_mon_shutdown_timeout);
+    is_stopping_cond.wait_for(l, timeout,
+      [this] { return get_state() == STOPPING; });
   }
   dout(0) << __func__ << " starting shutdown" << dendl;
   set_state(STOPPING);
@@ -1318,11 +1399,11 @@ bool OSDService::prepare_to_stop()
 
 void OSDService::got_stop_ack()
 {
-  Mutex::Locker l(is_stopping_lock);
+  std::scoped_lock l(is_stopping_lock);
   if (get_state() == PREPARING_TO_STOP) {
     dout(0) << __func__ << " starting shutdown" << dendl;
     set_state(STOPPING);
-    is_stopping_cond.Signal();
+    is_stopping_cond.notify_all();
   } else {
     dout(10) << __func__ << " ignoring msg" << dendl;
   }
@@ -1331,25 +1412,67 @@ void OSDService::got_stop_ack()
 MOSDMap *OSDService::build_incremental_map_msg(epoch_t since, epoch_t to,
                                                OSDSuperblock& sblock)
 {
-  MOSDMap *m = new MOSDMap(monc->get_fsid());
+  MOSDMap *m = new MOSDMap(monc->get_fsid(),
+                          osdmap->get_encoding_features());
   m->oldest_map = max_oldest_map;
   m->newest_map = sblock.newest_map;
 
-  for (epoch_t e = to; e > since; e--) {
+  int max = cct->_conf->osd_map_message_max;
+  ssize_t max_bytes = cct->_conf->osd_map_message_max_bytes;
+
+  if (since < m->oldest_map) {
+    // we don't have the next map the target wants, so start with a
+    // full map.
     bufferlist bl;
-    if (e > m->oldest_map && get_inc_map_bl(e, bl)) {
-      m->incremental_maps[e].claim(bl);
-    } else if (get_map_bl(e, bl)) {
-      m->maps[e].claim(bl);
-      break;
+    dout(10) << __func__ << " oldest map " << max_oldest_map << " > since "
+            << since << ", starting with full map" << dendl;
+    since = m->oldest_map;
+    if (!get_map_bl(since, bl)) {
+      derr << __func__ << " missing full map " << since << dendl;
+      goto panic;
+    }
+    max--;
+    max_bytes -= bl.length();
+    m->maps[since] = std::move(bl);
+  }
+  for (epoch_t e = since + 1; e <= to; ++e) {
+    bufferlist bl;
+    if (get_inc_map_bl(e, bl)) {
+      m->incremental_maps[e] = std::move(bl);
     } else {
-      derr << "since " << since << " to " << to
-          << " oldest " << m->oldest_map << " newest " << m->newest_map
-          << dendl;
-      m->put();
-      m = NULL;
-      break;
+      dout(10) << __func__ << " missing incremental map " << e << dendl;
+      if (!get_map_bl(e, bl)) {
+       derr << __func__ << " also missing full map " << e << dendl;
+       goto panic;
+      }
+      m->maps[e] = std::move(bl);
+    }
+    max--;
+    max_bytes -= bl.length();
+    if (max <= 0 || max_bytes <= 0) {
+      break;
+    }
+  }
+  return m;
+
+ panic:
+  if (!m->maps.empty() ||
+      !m->incremental_maps.empty()) {
+    // send what we have so far
+    return m;
+  }
+  // send something
+  bufferlist bl;
+  if (get_inc_map_bl(m->newest_map, bl)) {
+    m->incremental_maps[m->newest_map] = std::move(bl);
+  } else {
+    derr << __func__ << " unable to load latest map " << m->newest_map << dendl;
+    if (!get_map_bl(m->newest_map, bl)) {
+      derr << __func__ << " unable to load latest full map " << m->newest_map
+          << dendl;
+      ceph_abort();
     }
+    m->maps[m->newest_map] = std::move(bl);
   }
   return m;
 }
@@ -1360,7 +1483,7 @@ void OSDService::send_map(MOSDMap *m, Connection *con)
 }
 
 void OSDService::send_incremental_map(epoch_t since, Connection *con,
-                                      OSDMapRef& osdmap)
+                                      const OSDMapRef& osdmap)
 {
   epoch_t to = osdmap->get_epoch();
   dout(10) << "send_incremental_map " << since << " -> " << to
@@ -1371,7 +1494,8 @@ void OSDService::send_incremental_map(epoch_t since, Connection *con,
     OSDSuperblock sblock(get_superblock());
     if (since < sblock.oldest_map) {
       // just send latest full map
-      MOSDMap *m = new MOSDMap(monc->get_fsid());
+      MOSDMap *m = new MOSDMap(monc->get_fsid(),
+                              osdmap->get_encoding_features());
       m->oldest_map = max_oldest_map;
       m->newest_map = sblock.newest_map;
       get_map_bl(to, m->maps[to]);
@@ -1385,8 +1509,6 @@ void OSDService::send_incremental_map(epoch_t since, Connection *con,
       since = to - cct->_conf->osd_map_share_max_epochs;
     }
 
-    if (to - since > (epoch_t)cct->_conf->osd_map_message_max)
-      to = since + cct->_conf->osd_map_message_max;
     m = build_incremental_map_msg(since, to, sblock);
   }
   send_map(m, con);
@@ -1395,59 +1517,60 @@ void OSDService::send_incremental_map(epoch_t since, Connection *con,
 bool OSDService::_get_map_bl(epoch_t e, bufferlist& bl)
 {
   bool found = map_bl_cache.lookup(e, &bl);
-  if (found)
+  if (found) {
+    logger->inc(l_osd_map_bl_cache_hit);
     return true;
-  found = store->read(coll_t::meta(),
-                     OSD::get_osdmap_pobject_name(e), 0, 0, bl) >= 0;
-  if (found)
+  }
+  logger->inc(l_osd_map_bl_cache_miss);
+  found = store->read(meta_ch,
+                     OSD::get_osdmap_pobject_name(e), 0, 0, bl,
+                     CEPH_OSD_OP_FLAG_FADVISE_WILLNEED) >= 0;
+  if (found) {
     _add_map_bl(e, bl);
+  }
   return found;
 }
 
 bool OSDService::get_inc_map_bl(epoch_t e, bufferlist& bl)
 {
-  Mutex::Locker l(map_cache_lock);
+  std::lock_guard l(map_cache_lock);
   bool found = map_bl_inc_cache.lookup(e, &bl);
-  if (found)
+  if (found) {
+    logger->inc(l_osd_map_bl_cache_hit);
     return true;
-  found = store->read(coll_t::meta(),
-                     OSD::get_inc_osdmap_pobject_name(e), 0, 0, bl) >= 0;
-  if (found)
+  }
+  logger->inc(l_osd_map_bl_cache_miss);
+  found = store->read(meta_ch,
+                     OSD::get_inc_osdmap_pobject_name(e), 0, 0, bl,
+                     CEPH_OSD_OP_FLAG_FADVISE_WILLNEED) >= 0;
+  if (found) {
     _add_map_inc_bl(e, bl);
+  }
   return found;
 }
 
 void OSDService::_add_map_bl(epoch_t e, bufferlist& bl)
 {
   dout(10) << "add_map_bl " << e << " " << bl.length() << " bytes" << dendl;
+  // cache a contiguous buffer
+  if (bl.get_num_buffers() > 1) {
+    bl.rebuild();
+  }
+  bl.try_assign_to_mempool(mempool::mempool_osd_mapbl);
   map_bl_cache.add(e, bl);
 }
 
 void OSDService::_add_map_inc_bl(epoch_t e, bufferlist& bl)
 {
   dout(10) << "add_map_inc_bl " << e << " " << bl.length() << " bytes" << dendl;
+  // cache a contiguous buffer
+  if (bl.get_num_buffers() > 1) {
+    bl.rebuild();
+  }
+  bl.try_assign_to_mempool(mempool::mempool_osd_mapbl);
   map_bl_inc_cache.add(e, bl);
 }
 
-void OSDService::pin_map_inc_bl(epoch_t e, bufferlist &bl)
-{
-  Mutex::Locker l(map_cache_lock);
-  map_bl_inc_cache.pin(e, bl);
-}
-
-void OSDService::pin_map_bl(epoch_t e, bufferlist &bl)
-{
-  Mutex::Locker l(map_cache_lock);
-  map_bl_cache.pin(e, bl);
-}
-
-void OSDService::clear_map_bl_cache_pins(epoch_t e)
-{
-  Mutex::Locker l(map_cache_lock);
-  map_bl_inc_cache.clear_pinned(e);
-  map_bl_cache.clear_pinned(e);
-}
-
 OSDMapRef OSDService::_add_map(OSDMap *o)
 {
   epoch_t e = o->get_epoch();
@@ -1469,16 +1592,14 @@ OSDMapRef OSDService::_add_map(OSDMap *o)
 
 OSDMapRef OSDService::try_get_map(epoch_t epoch)
 {
-  Mutex::Locker l(map_cache_lock);
+  std::lock_guard l(map_cache_lock);
   OSDMapRef retval = map_cache.lookup(epoch);
   if (retval) {
     dout(30) << "get_map " << epoch << " -cached" << dendl;
-    if (logger) {
-      logger->inc(l_osd_map_cache_hit);
-    }
+    logger->inc(l_osd_map_cache_hit);
     return retval;
   }
-  if (logger) {
+  {
     logger->inc(l_osd_map_cache_miss);
     epoch_t lb = map_cache.cached_key_lower_bound();
     if (epoch < lb) {
@@ -1509,29 +1630,35 @@ OSDMapRef OSDService::try_get_map(epoch_t epoch)
 
 void OSDService::reply_op_error(OpRequestRef op, int err)
 {
-  reply_op_error(op, err, eversion_t(), 0);
+  reply_op_error(op, err, eversion_t(), 0, {});
 }
 
 void OSDService::reply_op_error(OpRequestRef op, int err, eversion_t v,
-                                version_t uv)
+                                version_t uv,
+                               vector<pg_log_op_return_item_t> op_returns)
 {
-  const MOSDOp *m = static_cast<const MOSDOp*>(op->get_req());
-  assert(m->get_type() == CEPH_MSG_OSD_OP);
+  auto m = op->get_req<MOSDOp>();
+  ceph_assert(m->get_type() == CEPH_MSG_OSD_OP);
   int flags;
   flags = m->get_flags() & (CEPH_OSD_FLAG_ACK|CEPH_OSD_FLAG_ONDISK);
 
   MOSDOpReply *reply = new MOSDOpReply(m, err, osdmap->get_epoch(), flags,
-                                      true);
+                                      !m->has_flag(CEPH_OSD_FLAG_RETURNVEC));
   reply->set_reply_versions(v, uv);
+  reply->set_op_returns(op_returns);
   m->get_connection()->send_message(reply);
 }
 
 void OSDService::handle_misdirected_op(PG *pg, OpRequestRef op)
 {
-  const MOSDOp *m = static_cast<const MOSDOp*>(op->get_req());
-  assert(m->get_type() == CEPH_MSG_OSD_OP);
+  if (!cct->_conf->osd_debug_misdirected_ops) {
+    return;
+  }
+
+  auto m = op->get_req<MOSDOp>();
+  ceph_assert(m->get_type() == CEPH_MSG_OSD_OP);
 
-  assert(m->get_map_epoch() >= pg->info.history.same_primary_since);
+  ceph_assert(m->get_map_epoch() >= pg->get_history().same_primary_since);
 
   if (pg->is_ec_pg()) {
     /**
@@ -1550,7 +1677,7 @@ void OSDService::handle_misdirected_op(PG *pg, OpRequestRef op)
        * splitting.  The simplest thing is to detect such cases here and drop
        * them without an error (the client will resend anyway).
        */
-    assert(m->get_map_epoch() <= superblock.newest_map);
+    ceph_assert(m->get_map_epoch() <= superblock.newest_map);
     OSDMapRef opmap = try_get_map(m->get_map_epoch());
     if (!opmap) {
       dout(7) << __func__ << ": " << *pg << " no longer have map for "
@@ -1562,7 +1689,7 @@ void OSDService::handle_misdirected_op(PG *pg, OpRequestRef op)
     if ((m->get_flags() & CEPH_OSD_FLAG_PGOP) == 0)
       _pgid = opmap->raw_pg_to_pg(_pgid);
     if (opmap->get_primary_shard(_pgid, &pgid) &&
-       pgid.shard != pg->info.pgid.shard) {
+       pgid.shard != pg->pg_id.shard) {
       dout(7) << __func__ << ": " << *pg << " primary changed since "
              << m->get_map_epoch() << ", dropping" << dendl;
       return;
@@ -1573,43 +1700,322 @@ void OSDService::handle_misdirected_op(PG *pg, OpRequestRef op)
   clog->warn() << m->get_source_inst() << " misdirected " << m->get_reqid()
               << " pg " << m->get_raw_pg()
               << " to osd." << whoami
-              << " not " << pg->acting
+              << " not " << pg->get_acting()
               << " in e" << m->get_map_epoch() << "/" << osdmap->get_epoch();
-  if (g_conf->osd_enxio_on_misdirected_op) {
-    reply_op_error(op, -ENXIO);
-  }
 }
 
-void OSDService::enqueue_back(spg_t pgid, PGQueueable qi)
+void OSDService::enqueue_back(OpSchedulerItem&& qi)
 {
-  osd->op_shardedwq.queue(make_pair(pgid, qi));
+  osd->op_shardedwq.queue(std::move(qi));
 }
 
-void OSDService::enqueue_front(spg_t pgid, PGQueueable qi)
+void OSDService::enqueue_front(OpSchedulerItem&& qi)
 {
-  osd->op_shardedwq.queue_front(make_pair(pgid, qi));
+  osd->op_shardedwq.queue_front(std::move(qi));
 }
 
-void OSDService::queue_for_peering(PG *pg)
+void OSDService::queue_recovery_context(
+  PG *pg,
+  GenContext<ThreadPool::TPHandle&> *c)
 {
-  peering_wq.queue(pg);
+  epoch_t e = get_osdmap_epoch();
+  enqueue_back(
+    OpSchedulerItem(
+      unique_ptr<OpSchedulerItem::OpQueueable>(
+       new PGRecoveryContext(pg->get_pgid(), c, e)),
+      cct->_conf->osd_recovery_cost,
+      cct->_conf->osd_recovery_priority,
+      ceph_clock_now(),
+      0,
+      e));
 }
 
 void OSDService::queue_for_snap_trim(PG *pg)
 {
   dout(10) << "queueing " << *pg << " for snaptrim" << dendl;
-  osd->op_shardedwq.queue(
-    make_pair(
-      pg->info.pgid,
-      PGQueueable(
-       PGSnapTrim(pg->get_osdmap()->get_epoch()),
-       cct->_conf->osd_snap_trim_cost,
-       cct->_conf->osd_snap_trim_priority,
-       ceph_clock_now(),
-       entity_inst_t(),
-       pg->get_osdmap()->get_epoch())));
+  enqueue_back(
+    OpSchedulerItem(
+      unique_ptr<OpSchedulerItem::OpQueueable>(
+       new PGSnapTrim(pg->get_pgid(), pg->get_osdmap_epoch())),
+      cct->_conf->osd_snap_trim_cost,
+      cct->_conf->osd_snap_trim_priority,
+      ceph_clock_now(),
+      0,
+      pg->get_osdmap_epoch()));
+}
+
+template <class MSG_TYPE>
+void OSDService::queue_scrub_event_msg(PG* pg,
+                                      Scrub::scrub_prio_t with_priority,
+                                      unsigned int qu_priority)
+{
+  const auto epoch = pg->get_osdmap_epoch();
+  auto msg = new MSG_TYPE(pg->get_pgid(), epoch);
+  dout(15) << "queue a scrub event (" << *msg << ") for " << *pg << ". Epoch: " << epoch << dendl;
+
+  enqueue_back(OpSchedulerItem(
+    unique_ptr<OpSchedulerItem::OpQueueable>(msg), cct->_conf->osd_scrub_cost,
+    pg->scrub_requeue_priority(with_priority, qu_priority), ceph_clock_now(), 0, epoch));
+}
+
+template <class MSG_TYPE>
+void OSDService::queue_scrub_event_msg(PG* pg, Scrub::scrub_prio_t with_priority)
+{
+  const auto epoch = pg->get_osdmap_epoch();
+  auto msg = new MSG_TYPE(pg->get_pgid(), epoch);
+  dout(15) << "queue a scrub event (" << *msg << ") for " << *pg << ". Epoch: " << epoch << dendl;
+
+  enqueue_back(OpSchedulerItem(
+    unique_ptr<OpSchedulerItem::OpQueueable>(msg), cct->_conf->osd_scrub_cost,
+    pg->scrub_requeue_priority(with_priority), ceph_clock_now(), 0, epoch));
+}
+
+void OSDService::queue_for_scrub(PG* pg, Scrub::scrub_prio_t with_priority)
+{
+  queue_scrub_event_msg<PGScrub>(pg, with_priority);
+}
+
+void OSDService::queue_scrub_after_repair(PG* pg, Scrub::scrub_prio_t with_priority)
+{
+  queue_scrub_event_msg<PGScrubAfterRepair>(pg, with_priority);
+}
+
+void OSDService::queue_for_rep_scrub(PG* pg,
+                                    Scrub::scrub_prio_t with_priority,
+                                    unsigned int qu_priority)
+{
+  queue_scrub_event_msg<PGRepScrub>(pg, with_priority, qu_priority);
+}
+
+void OSDService::queue_for_rep_scrub_resched(PG* pg,
+                                            Scrub::scrub_prio_t with_priority,
+                                            unsigned int qu_priority)
+{
+  // Resulting scrub event: 'SchedReplica'
+  queue_scrub_event_msg<PGRepScrubResched>(pg, with_priority, qu_priority);
+}
+
+void OSDService::queue_for_scrub_granted(PG* pg, Scrub::scrub_prio_t with_priority)
+{
+  // Resulting scrub event: 'RemotesReserved'
+  queue_scrub_event_msg<PGScrubResourcesOK>(pg, with_priority);
+}
+
+void OSDService::queue_for_scrub_denied(PG* pg, Scrub::scrub_prio_t with_priority)
+{
+  // Resulting scrub event: 'ReservationFailure'
+  queue_scrub_event_msg<PGScrubDenied>(pg, with_priority);
+}
+
+void OSDService::queue_for_scrub_resched(PG* pg, Scrub::scrub_prio_t with_priority)
+{
+  // Resulting scrub event: 'InternalSchedScrub'
+  queue_scrub_event_msg<PGScrubResched>(pg, with_priority);
+}
+
+void OSDService::queue_scrub_pushes_update(PG* pg, Scrub::scrub_prio_t with_priority)
+{
+  // Resulting scrub event: 'ActivePushesUpd'
+  queue_scrub_event_msg<PGScrubPushesUpdate>(pg, with_priority);
+}
+
+void OSDService::queue_scrub_applied_update(PG* pg, Scrub::scrub_prio_t with_priority)
+{
+  queue_scrub_event_msg<PGScrubAppliedUpdate>(pg, with_priority);
+}
+
+void OSDService::queue_scrub_unblocking(PG* pg, Scrub::scrub_prio_t with_priority)
+{
+  // Resulting scrub event: 'Unblocked'
+  queue_scrub_event_msg<PGScrubUnblocked>(pg, with_priority);
+}
+
+void OSDService::queue_scrub_digest_update(PG* pg, Scrub::scrub_prio_t with_priority)
+{
+  // Resulting scrub event: 'DigestUpdate'
+  queue_scrub_event_msg<PGScrubDigestUpdate>(pg, with_priority);
+}
+
+void OSDService::queue_scrub_got_repl_maps(PG* pg, Scrub::scrub_prio_t with_priority)
+{
+  // Resulting scrub event: 'GotReplicas'
+  queue_scrub_event_msg<PGScrubGotReplMaps>(pg, with_priority);
+}
+
+void OSDService::queue_scrub_replica_pushes(PG *pg, Scrub::scrub_prio_t with_priority)
+{
+  // Resulting scrub event: 'ReplicaPushesUpd'
+  queue_scrub_event_msg<PGScrubReplicaPushes>(pg, with_priority);
+}
+
+void OSDService::queue_for_pg_delete(spg_t pgid, epoch_t e)
+{
+  dout(10) << __func__ << " on " << pgid << " e " << e  << dendl;
+  enqueue_back(
+    OpSchedulerItem(
+      unique_ptr<OpSchedulerItem::OpQueueable>(
+       new PGDelete(pgid, e)),
+      cct->_conf->osd_pg_delete_cost,
+      cct->_conf->osd_pg_delete_priority,
+      ceph_clock_now(),
+      0,
+      e));
+}
+
+bool OSDService::try_finish_pg_delete(PG *pg, unsigned old_pg_num)
+{
+  return osd->try_finish_pg_delete(pg, old_pg_num);
+}
+
+// ---
+
+void OSDService::set_ready_to_merge_source(PG *pg, eversion_t version)
+{
+  std::lock_guard l(merge_lock);
+  dout(10) << __func__ << " " << pg->pg_id << dendl;
+  ready_to_merge_source[pg->pg_id.pgid] = version;
+  assert(not_ready_to_merge_source.count(pg->pg_id.pgid) == 0);
+  _send_ready_to_merge();
+}
+
+void OSDService::set_ready_to_merge_target(PG *pg,
+                                          eversion_t version,
+                                          epoch_t last_epoch_started,
+                                          epoch_t last_epoch_clean)
+{
+  std::lock_guard l(merge_lock);
+  dout(10) << __func__ << " " << pg->pg_id << dendl;
+  ready_to_merge_target.insert(make_pair(pg->pg_id.pgid,
+                                        make_tuple(version,
+                                                   last_epoch_started,
+                                                   last_epoch_clean)));
+  assert(not_ready_to_merge_target.count(pg->pg_id.pgid) == 0);
+  _send_ready_to_merge();
+}
+
+void OSDService::set_not_ready_to_merge_source(pg_t source)
+{
+  std::lock_guard l(merge_lock);
+  dout(10) << __func__ << " " << source << dendl;
+  not_ready_to_merge_source.insert(source);
+  assert(ready_to_merge_source.count(source) == 0);
+  _send_ready_to_merge();
 }
 
+void OSDService::set_not_ready_to_merge_target(pg_t target, pg_t source)
+{
+  std::lock_guard l(merge_lock);
+  dout(10) << __func__ << " " << target << " source " << source << dendl;
+  not_ready_to_merge_target[target] = source;
+  assert(ready_to_merge_target.count(target) == 0);
+  _send_ready_to_merge();
+}
+
+void OSDService::send_ready_to_merge()
+{
+  std::lock_guard l(merge_lock);
+  _send_ready_to_merge();
+}
+
+void OSDService::_send_ready_to_merge()
+{
+  dout(20) << __func__
+          << " ready_to_merge_source " << ready_to_merge_source
+          << " not_ready_to_merge_source " << not_ready_to_merge_source
+          << " ready_to_merge_target " << ready_to_merge_target
+          << " not_ready_to_merge_target " << not_ready_to_merge_target
+          << " sent_ready_to_merge_source " << sent_ready_to_merge_source
+          << dendl;
+  for (auto src : not_ready_to_merge_source) {
+    if (sent_ready_to_merge_source.count(src) == 0) {
+      monc->send_mon_message(new MOSDPGReadyToMerge(
+                              src,
+                              {}, {}, 0, 0,
+                              false,
+                              osdmap->get_epoch()));
+      sent_ready_to_merge_source.insert(src);
+    }
+  }
+  for (auto p : not_ready_to_merge_target) {
+    if (sent_ready_to_merge_source.count(p.second) == 0) {
+      monc->send_mon_message(new MOSDPGReadyToMerge(
+                              p.second,
+                              {}, {}, 0, 0,
+                              false,
+                              osdmap->get_epoch()));
+      sent_ready_to_merge_source.insert(p.second);
+    }
+  }
+  for (auto src : ready_to_merge_source) {
+    if (not_ready_to_merge_source.count(src.first) ||
+       not_ready_to_merge_target.count(src.first.get_parent())) {
+      continue;
+    }
+    auto p = ready_to_merge_target.find(src.first.get_parent());
+    if (p != ready_to_merge_target.end() &&
+       sent_ready_to_merge_source.count(src.first) == 0) {
+      monc->send_mon_message(new MOSDPGReadyToMerge(
+                              src.first,           // source pgid
+                              src.second,          // src version
+                              std::get<0>(p->second), // target version
+                              std::get<1>(p->second), // PG's last_epoch_started
+                              std::get<2>(p->second), // PG's last_epoch_clean
+                              true,
+                              osdmap->get_epoch()));
+      sent_ready_to_merge_source.insert(src.first);
+    }
+  }
+}
+
+void OSDService::clear_ready_to_merge(PG *pg)
+{
+  std::lock_guard l(merge_lock);
+  dout(10) << __func__ << " " << pg->pg_id << dendl;
+  ready_to_merge_source.erase(pg->pg_id.pgid);
+  ready_to_merge_target.erase(pg->pg_id.pgid);
+  not_ready_to_merge_source.erase(pg->pg_id.pgid);
+  not_ready_to_merge_target.erase(pg->pg_id.pgid);
+  sent_ready_to_merge_source.erase(pg->pg_id.pgid);
+}
+
+void OSDService::clear_sent_ready_to_merge()
+{
+  std::lock_guard l(merge_lock);
+  sent_ready_to_merge_source.clear();
+}
+
+void OSDService::prune_sent_ready_to_merge(const OSDMapRef& osdmap)
+{
+  std::lock_guard l(merge_lock);
+  auto i = sent_ready_to_merge_source.begin();
+  while (i != sent_ready_to_merge_source.end()) {
+    if (!osdmap->pg_exists(*i)) {
+      dout(10) << __func__ << " " << *i << dendl;
+      i = sent_ready_to_merge_source.erase(i);
+    } else {
+      ++i;
+    }
+  }
+}
+
+// ---
+
+void OSDService::_queue_for_recovery(
+  std::pair<epoch_t, PGRef> p,
+  uint64_t reserved_pushes)
+{
+  ceph_assert(ceph_mutex_is_locked_by_me(recovery_lock));
+  enqueue_back(
+    OpSchedulerItem(
+      unique_ptr<OpSchedulerItem::OpQueueable>(
+       new PGRecovery(
+         p.second->get_pgid(), p.first, reserved_pushes)),
+      cct->_conf->osd_recovery_cost,
+      cct->_conf->osd_recovery_priority,
+      ceph_clock_now(),
+      0,
+      p.first));
+}
 
 // ====================================================================
 // OSD
@@ -1618,48 +2024,50 @@ void OSDService::queue_for_snap_trim(PG *pg)
 #define dout_prefix *_dout
 
 // Commands shared between OSD's console and admin console:
-namespace ceph { 
-namespace osd_cmds { 
+namespace ceph::osd_cmds {
 
-int heap(CephContext& cct, cmdmap_t& cmdmap, Formatter& f, std::ostream& os);
-}} // namespace ceph::osd_cmds
+int heap(CephContext& cct, const cmdmap_t& cmdmap, Formatter& f, std::ostream& os);
 
-int OSD::mkfs(CephContext *cct, ObjectStore *store, const string &dev,
-             uuid_d fsid, int whoami)
+} // namespace ceph::osd_cmds
+
+int OSD::mkfs(CephContext *cct, ObjectStore *store, uuid_d fsid, int whoami, string osdspec_affinity)
 {
   int ret;
 
-  ceph::shared_ptr<ObjectStore::Sequencer> osr(
-    new ObjectStore::Sequencer("mkfs"));
   OSDSuperblock sb;
   bufferlist sbbl;
-  C_SaferCond waiter;
+  ObjectStore::CollectionHandle ch;
 
   // if we are fed a uuid for this osd, use it.
   store->set_fsid(cct->_conf->osd_uuid);
 
   ret = store->mkfs();
   if (ret) {
-    derr << "OSD::mkfs: ObjectStore::mkfs failed with error " << ret << dendl;
+    derr << "OSD::mkfs: ObjectStore::mkfs failed with error "
+         << cpp_strerror(ret) << dendl;
     goto free_store;
   }
 
-  store->set_cache_shards(cct->_conf->osd_op_num_shards);
+  store->set_cache_shards(1);  // doesn't matter for mkfs!
 
   ret = store->mount();
   if (ret) {
-    derr << "OSD::mkfs: couldn't mount ObjectStore: error " << ret << dendl;
+    derr << "OSD::mkfs: couldn't mount ObjectStore: error "
+         << cpp_strerror(ret) << dendl;
     goto free_store;
   }
 
-  ret = store->read(coll_t::meta(), OSD_SUPERBLOCK_GOBJECT, 0, 0, sbbl);
-  if (ret >= 0) {
+  ch = store->open_collection(coll_t::meta());
+  if (ch) {
+    ret = store->read(ch, OSD_SUPERBLOCK_GOBJECT, 0, 0, sbbl);
+    if (ret < 0) {
+      derr << "OSD::mkfs: have meta collection but no superblock" << dendl;
+      goto free_store;
+    }
     /* if we already have superblock, check content of superblock */
     dout(0) << " have superblock" << dendl;
-    bufferlist::iterator p;
-    p = sbbl.begin();
-    ::decode(sb, p);
+    auto p = sbbl.cbegin();
+    decode(sb, p);
     if (whoami != sb.whoami) {
       derr << "provided osd id " << whoami << " != superblock's " << sb.whoami
           << dendl;
@@ -1680,37 +2088,39 @@ int OSD::mkfs(CephContext *cct, ObjectStore *store, const string &dev,
     sb.compat_features = get_osd_initial_compat_set();
 
     bufferlist bl;
-    ::encode(sb, bl);
+    encode(sb, bl);
 
+    ObjectStore::CollectionHandle ch = store->create_new_collection(
+      coll_t::meta());
     ObjectStore::Transaction t;
     t.create_collection(coll_t::meta(), 0);
     t.write(coll_t::meta(), OSD_SUPERBLOCK_GOBJECT, 0, bl.length(), bl);
-    ret = store->apply_transaction(osr.get(), std::move(t));
+    ret = store->queue_transaction(ch, std::move(t));
     if (ret) {
       derr << "OSD::mkfs: error while writing OSD_SUPERBLOCK_GOBJECT: "
-          << "apply_transaction returned " << ret << dendl;
+          << "queue_transaction returned " << cpp_strerror(ret) << dendl;
       goto umount_store;
     }
   }
 
-  if (!osr->flush_commit(&waiter)) {
-    waiter.wait();
-  }
-
-  ret = write_meta(store, sb.cluster_fsid, sb.osd_fsid, whoami);
+  ret = write_meta(cct, store, sb.cluster_fsid, sb.osd_fsid, whoami, osdspec_affinity);
   if (ret) {
-    derr << "OSD::mkfs: failed to write fsid file: error " << ret << dendl;
+    derr << "OSD::mkfs: failed to write fsid file: error "
+         << cpp_strerror(ret) << dendl;
     goto umount_store;
   }
 
 umount_store:
+  if (ch) {
+    ch.reset();
+  }
   store->umount();
 free_store:
   delete store;
   return ret;
 }
 
-int OSD::write_meta(ObjectStore *store, uuid_d& cluster_fsid, uuid_d& osd_fsid, int whoami)
+int OSD::write_meta(CephContext *cct, ObjectStore *store, uuid_d& cluster_fsid, uuid_d& osd_fsid, int whoami, string& osdspec_affinity)
 {
   char val[80];
   int r;
@@ -1730,6 +2140,33 @@ int OSD::write_meta(ObjectStore *store, uuid_d& cluster_fsid, uuid_d& osd_fsid,
   if (r < 0)
     return r;
 
+  string key = cct->_conf.get_val<string>("key");
+  if (key.size()) {
+    r = store->write_meta("osd_key", key);
+    if (r < 0)
+      return r;
+  } else {
+    string keyfile = cct->_conf.get_val<string>("keyfile");
+    if (!keyfile.empty()) {
+      bufferlist keybl;
+      string err;
+      r = keybl.read_file(keyfile.c_str(), &err);
+      if (r < 0) {
+       derr << __func__ << " failed to read keyfile " << keyfile << ": "
+            << err << ": " << cpp_strerror(r) << dendl;
+       return r;
+      }
+      r = store->write_meta("osd_key", keybl.to_str());
+      if (r < 0)
+       return r;
+    }
+  }
+  if (!osdspec_affinity.empty()) {
+    r = store->write_meta("osdspec_affinity", osdspec_affinity.c_str());
+    if (r < 0)
+      return r;
+  }
+
   r = store->write_meta("ready", "ready");
   if (r < 0)
     return r;
@@ -1737,37 +2174,46 @@ int OSD::write_meta(ObjectStore *store, uuid_d& cluster_fsid, uuid_d& osd_fsid,
   return 0;
 }
 
-int OSD::peek_meta(ObjectStore *store, std::string& magic,
-                  uuid_d& cluster_fsid, uuid_d& osd_fsid, int& whoami)
+int OSD::peek_meta(ObjectStore *store,
+                  std::string *magic,
+                  uuid_d *cluster_fsid,
+                  uuid_d *osd_fsid,
+                  int *whoami,
+                  ceph_release_t *require_osd_release)
 {
   string val;
 
   int r = store->read_meta("magic", &val);
   if (r < 0)
     return r;
-  magic = val;
+  *magic = val;
 
   r = store->read_meta("whoami", &val);
   if (r < 0)
     return r;
-  whoami = atoi(val.c_str());
+  *whoami = atoi(val.c_str());
 
   r = store->read_meta("ceph_fsid", &val);
   if (r < 0)
     return r;
-  r = cluster_fsid.parse(val.c_str());
+  r = cluster_fsid->parse(val.c_str());
   if (!r)
     return -EINVAL;
 
   r = store->read_meta("fsid", &val);
   if (r < 0) {
-    osd_fsid = uuid_d();
+    *osd_fsid = uuid_d();
   } else {
-    r = osd_fsid.parse(val.c_str());
+    r = osd_fsid->parse(val.c_str());
     if (!r)
       return -EINVAL;
   }
 
+  r = store->read_meta("require_osd_release", &val);
+  if (r >= 0) {
+    *require_osd_release = ceph_release_from_name(val);
+  }
+
   return 0;
 }
 
@@ -1787,42 +2233,32 @@ OSD::OSD(CephContext *cct_, ObjectStore *store_,
         Messenger *hb_back_serverm,
         Messenger *osdc_messenger,
         MonClient *mc,
-        const std::string &dev, const std::string &jdev) :
+        const std::string &dev, const std::string &jdev,
+        ceph::async::io_context_pool& poolctx) :
   Dispatcher(cct_),
-  osd_lock("OSD::osd_lock"),
   tick_timer(cct, osd_lock),
-  tick_timer_lock("OSD::tick_timer_lock"),
   tick_timer_without_osd_lock(cct, tick_timer_lock),
-  authorize_handler_cluster_registry(new AuthAuthorizeHandlerRegistry(cct,
-                                                                     cct->_conf->auth_supported.empty() ?
-                                                                     cct->_conf->auth_cluster_required :
-                                                                     cct->_conf->auth_supported)),
-  authorize_handler_service_registry(new AuthAuthorizeHandlerRegistry(cct,
-                                                                     cct->_conf->auth_supported.empty() ?
-                                                                     cct->_conf->auth_service_required :
-                                                                     cct->_conf->auth_supported)),
+  gss_ktfile_client(cct->_conf.get_val<std::string>("gss_ktab_client_file")),
   cluster_messenger(internal_messenger),
   client_messenger(external_messenger),
   objecter_messenger(osdc_messenger),
   monc(mc),
-  mgrc(cct_, client_messenger),
-  logger(NULL),
-  recoverystate_perf(NULL),
+  mgrc(cct_, client_messenger, &mc->monmap),
+  logger(create_logger()),
+  recoverystate_perf(create_recoverystate_perf()),
   store(store_),
   log_client(cct, client_messenger, &mc->monmap, LogClient::NO_FLAGS),
   clog(log_client.create_channel()),
   whoami(id),
   dev_path(dev), journal_path(jdev),
+  store_is_rotational(store->is_rotational()),
   trace_endpoint("0.0.0.0", 0, "osd"),
   asok_hook(NULL),
+  m_osd_pg_epoch_max_lag_factor(cct->_conf.get_val<double>(
+                                 "osd_pg_epoch_max_lag_factor")),
   osd_compat(get_osd_compat_set()),
-  osd_tp(cct, "OSD::osd_tp", "tp_osd", cct->_conf->osd_op_threads, "osd_op_threads"),
   osd_op_tp(cct, "OSD::osd_op_tp", "tp_osd_tp",
-    cct->_conf->osd_op_num_threads_per_shard * cct->_conf->osd_op_num_shards),
-  disk_tp(cct, "OSD::disk_tp", "tp_osd_disk", cct->_conf->osd_disk_threads, "osd_disk_threads"),
-  command_tp(cct, "OSD::command_tp", "tp_osd_cmd",  1),
-  session_waiting_lock("OSD::session_waiting_lock"),
-  heartbeat_lock("OSD::heartbeat_lock"),
+           get_num_op_threads()),
   heartbeat_stop(false),
   heartbeat_need_update(true),
   hb_front_client_messenger(hb_client_front),
@@ -1835,43 +2271,35 @@ OSD::OSD(CephContext *cct_, ObjectStore *store_,
   op_tracker(cct, cct->_conf->osd_enable_op_tracker,
                   cct->_conf->osd_num_op_tracker_shard),
   test_ops_hook(NULL),
-  op_queue(get_io_queue()),
-  op_prio_cutoff(get_io_prio_cut()),
   op_shardedwq(
-    cct->_conf->osd_op_num_shards,
     this,
-    cct->_conf->osd_op_thread_timeout,
-    cct->_conf->osd_op_thread_suicide_timeout,
+    ceph::make_timespan(cct->_conf->osd_op_thread_timeout),
+    ceph::make_timespan(cct->_conf->osd_op_thread_suicide_timeout),
     &osd_op_tp),
-  peering_wq(
-    this,
-    cct->_conf->osd_op_thread_timeout,
-    cct->_conf->osd_op_thread_suicide_timeout,
-    &osd_tp),
-  map_lock("OSD::map_lock"),
-  pg_map_lock("OSD::pg_map_lock"),
   last_pg_create_epoch(0),
-  mon_report_lock("OSD::mon_report_lock"),
-  stats_ack_timeout(cct->_conf->osd_mon_ack_timeout),
+  boot_finisher(cct),
   up_thru_wanted(0),
   requested_full_first(0),
   requested_full_last(0),
-  pg_stat_queue_lock("OSD::pg_stat_queue_lock"),
-  osd_stat_updated(false),
-  pg_stat_tid(0), pg_stat_tid_flushed(0),
-  command_wq(
-    this,
-    cct->_conf->osd_command_thread_timeout,
-    cct->_conf->osd_command_thread_suicide_timeout,
-    &command_tp),
-  remove_wq(
-    cct,
-    store,
-    cct->_conf->osd_remove_thread_timeout,
-    cct->_conf->osd_remove_thread_suicide_timeout,
-    &disk_tp),
-  service(this)
+  service(this, poolctx)
 {
+
+  if (!gss_ktfile_client.empty()) {
+    // Assert we can export environment variable
+    /*
+        The default client keytab is used, if it is present and readable,
+        to automatically obtain initial credentials for GSSAPI client
+        applications. The principal name of the first entry in the client
+        keytab is used by default when obtaining initial credentials.
+        1. The KRB5_CLIENT_KTNAME environment variable.
+        2. The default_client_keytab_name profile variable in [libdefaults].
+        3. The hardcoded default, DEFCKTNAME.
+    */
+    const int32_t set_result(setenv("KRB5_CLIENT_KTNAME",
+                                    gss_ktfile_client.c_str(), 1));
+    ceph_assert(set_result == 0);
+  }
+
   monc->set_messenger(client_messenger);
   op_tracker.set_complaint_and_threshold(cct->_conf->osd_op_complaint_time,
                                          cct->_conf->osd_op_log_threshold);
@@ -1879,18 +2307,33 @@ OSD::OSD(CephContext *cct_, ObjectStore *store_,
                                            cct->_conf->osd_op_history_duration);
   op_tracker.set_history_slow_op_size_and_threshold(cct->_conf->osd_op_history_slow_op_size,
                                                     cct->_conf->osd_op_history_slow_op_threshold);
+  ObjectCleanRegions::set_max_num_intervals(cct->_conf->osd_object_clean_region_max_num_intervals);
 #ifdef WITH_BLKIN
   std::stringstream ss;
   ss << "osd." << whoami;
   trace_endpoint.copy_name(ss.str());
 #endif
+
+  // initialize shards
+  num_shards = get_num_op_shards();
+  for (uint32_t i = 0; i < num_shards; i++) {
+    OSDShard *one_shard = new OSDShard(
+      i,
+      cct,
+      this);
+    shards.push_back(one_shard);
+  }
+
+  // override some config options if mclock is enabled on all the shards
+  maybe_override_options_for_qos();
 }
 
 OSD::~OSD()
 {
-  delete authorize_handler_cluster_registry;
-  delete authorize_handler_service_registry;
-  delete class_handler;
+  while (!shards.empty()) {
+    delete shards.back();
+    shards.pop_back();
+  }
   cct->get_perfcounters_collection()->remove(recoverystate_perf);
   cct->get_perfcounters_collection()->remove(logger);
   delete recoverystate_perf;
@@ -1898,18 +2341,24 @@ OSD::~OSD()
   delete store;
 }
 
-void cls_initialize(ClassHandler *ch);
+double OSD::get_tick_interval() const
+{
+  // vary +/- 5% to avoid scrub scheduling livelocks
+  constexpr auto delta = 0.05;
+  return (OSD_TICK_INTERVAL *
+         ceph::util::generate_random_number(1.0 - delta, 1.0 + delta));
+}
 
 void OSD::handle_signal(int signum)
 {
-  assert(signum == SIGINT || signum == SIGTERM);
+  ceph_assert(signum == SIGINT || signum == SIGTERM);
   derr << "*** Got signal " << sig_str(signum) << " ***" << dendl;
   shutdown();
 }
 
 int OSD::pre_init()
 {
-  Mutex::Locker lock(osd_lock);
+  std::lock_guard lock(osd_lock);
   if (is_stopping())
     return 0;
 
@@ -1919,7 +2368,88 @@ int OSD::pre_init()
     return -EBUSY;
   }
 
-  cct->_conf->add_observer(this);
+  cct->_conf.add_observer(this);
+  return 0;
+}
+
+int OSD::set_numa_affinity()
+{
+  // storage numa node
+  int store_node = -1;
+  store->get_numa_node(&store_node, nullptr, nullptr);
+  if (store_node >= 0) {
+    dout(1) << __func__ << " storage numa node " << store_node << dendl;
+  }
+
+  // check network numa node(s)
+  int front_node = -1, back_node = -1;
+  string front_iface = pick_iface(
+    cct,
+    client_messenger->get_myaddrs().front().get_sockaddr_storage());
+  string back_iface = pick_iface(
+    cct,
+    cluster_messenger->get_myaddrs().front().get_sockaddr_storage());
+  int r = get_iface_numa_node(front_iface, &front_node);
+  if (r >= 0 && front_node >= 0) {
+    dout(1) << __func__ << " public network " << front_iface << " numa node "
+            << front_node << dendl;
+    r = get_iface_numa_node(back_iface, &back_node);
+    if (r >= 0 && back_node >= 0) {
+      dout(1) << __func__ << " cluster network " << back_iface << " numa node "
+             << back_node << dendl;
+      if (front_node == back_node &&
+         front_node == store_node) {
+       dout(1) << " objectstore and network numa nodes all match" << dendl;
+       if (g_conf().get_val<bool>("osd_numa_auto_affinity")) {
+         numa_node = front_node;
+       }
+      } else if (front_node != back_node) {
+        dout(1) << __func__ << " public and cluster network numa nodes do not match"
+                << dendl;
+      } else {
+       dout(1) << __func__ << " objectstore and network numa nodes do not match"
+               << dendl;
+      }
+    } else if (back_node == -2) {
+      dout(1) << __func__ << " cluster network " << back_iface
+              << " ports numa nodes do not match" << dendl;
+    } else {
+      derr << __func__ << " unable to identify cluster interface '" << back_iface
+           << "' numa node: " << cpp_strerror(r) << dendl;
+    }
+  } else if (front_node == -2) {
+    dout(1) << __func__ << " public network " << front_iface
+            << " ports numa nodes do not match" << dendl;
+  } else {
+    derr << __func__ << " unable to identify public interface '" << front_iface
+        << "' numa node: " << cpp_strerror(r) << dendl;
+  }
+  if (int node = g_conf().get_val<int64_t>("osd_numa_node"); node >= 0) {
+    // this takes precedence over the automagic logic above
+    numa_node = node;
+  }
+  if (numa_node >= 0) {
+    int r = get_numa_node_cpu_set(numa_node, &numa_cpu_set_size, &numa_cpu_set);
+    if (r < 0) {
+      dout(1) << __func__ << " unable to determine numa node " << numa_node
+             << " CPUs" << dendl;
+      numa_node = -1;
+    } else {
+      dout(1) << __func__ << " setting numa affinity to node " << numa_node
+             << " cpus "
+             << cpu_set_to_str_list(numa_cpu_set_size, &numa_cpu_set)
+             << dendl;
+      r = set_cpu_affinity_all_threads(numa_cpu_set_size, &numa_cpu_set);
+      if (r < 0) {
+       r = -errno;
+       derr << __func__ << " failed to set numa affinity: " << cpp_strerror(r)
+            << dendl;
+       numa_node = -1;
+      }
+    }
+  } else {
+    dout(1) << __func__ << " not setting numa affinity" << dendl;
+  }
   return 0;
 }
 
@@ -1929,20 +2459,102 @@ class OSDSocketHook : public AdminSocketHook {
   OSD *osd;
 public:
   explicit OSDSocketHook(OSD *o) : osd(o) {}
-  bool call(std::string admin_command, cmdmap_t& cmdmap, std::string format,
-           bufferlist& out) override {
-    stringstream ss;
-    bool r = osd->asok_command(admin_command, cmdmap, format, ss);
-    out.append(ss);
-    return r;
+  int call(std::string_view prefix, const cmdmap_t& cmdmap,
+          Formatter *f,
+          std::ostream& ss,
+          bufferlist& out) override {
+    ceph_abort("should use async hook");
+  }
+  void call_async(
+    std::string_view prefix,
+    const cmdmap_t& cmdmap,
+    Formatter *f,
+    const bufferlist& inbl,
+    std::function<void(int,const std::string&,bufferlist&)> on_finish) override {
+    try {
+      osd->asok_command(prefix, cmdmap, f, inbl, on_finish);
+    } catch (const TOPNSPC::common::bad_cmd_get& e) {
+      bufferlist empty;
+      on_finish(-EINVAL, e.what(), empty);
+    }
   }
 };
 
-bool OSD::asok_command(string admin_command, cmdmap_t& cmdmap, string format,
-                      ostream& ss)
+std::set<int64_t> OSD::get_mapped_pools()
+{
+  std::set<int64_t> pools;
+  std::vector<spg_t> pgids;
+  _get_pgids(&pgids);
+  for (const auto &pgid : pgids) {
+    pools.insert(pgid.pool());
+  }
+  return pools;
+}
+
+void OSD::asok_command(
+  std::string_view prefix, const cmdmap_t& cmdmap,
+  Formatter *f,
+  const bufferlist& inbl,
+  std::function<void(int,const std::string&,bufferlist&)> on_finish)
 {
-  Formatter *f = Formatter::create(format, "json-pretty", "json-pretty");
-  if (admin_command == "status") {
+  int ret = 0;
+  stringstream ss;   // stderr error message stream
+  bufferlist outbl;  // if empty at end, we'll dump formatter as output
+
+  // --- PG commands are routed here to PG::do_command ---
+  if (prefix == "pg" ||
+      prefix == "query" ||
+      prefix == "mark_unfound_lost" ||
+      prefix == "list_unfound" ||
+      prefix == "scrub" ||
+      prefix == "deep_scrub"
+    ) {
+    string pgidstr;
+    pg_t pgid;
+    if (!cmd_getval(cmdmap, "pgid", pgidstr)) {
+      ss << "no pgid specified";
+      ret = -EINVAL;
+      goto out;
+    }
+    if (!pgid.parse(pgidstr.c_str())) {
+      ss << "couldn't parse pgid '" << pgidstr << "'";
+      ret = -EINVAL;
+      goto out;
+    }
+    spg_t pcand;
+    PGRef pg;
+    if (get_osdmap()->get_primary_shard(pgid, &pcand) &&
+       (pg = _lookup_lock_pg(pcand))) {
+      if (pg->is_primary()) {
+       cmdmap_t new_cmdmap = cmdmap;
+       try {
+         pg->do_command(prefix, new_cmdmap, inbl, on_finish);
+         pg->unlock();
+         return; // the pg handler calls on_finish directly
+       } catch (const TOPNSPC::common::bad_cmd_get& e) {
+         pg->unlock();
+         ss << e.what();
+         ret = -EINVAL;
+         goto out;
+       }
+      } else {
+       ss << "not primary for pgid " << pgid;
+       // do not reply; they will get newer maps and realize they
+       // need to resend.
+       pg->unlock();
+       ret = -EAGAIN;
+       goto out;
+      }
+    } else {
+      ss << "i don't have pgid " << pgid;
+      ret = -ENOENT;
+    }
+  }
+
+  // --- OSD commands follow ---
+
+  else if (prefix == "status") {
+    lock_guard l(osd_lock);
     f->open_object_section("status");
     f->dump_stream("cluster_fsid") << superblock.cluster_fsid;
     f->dump_stream("osd_fsid") << superblock.osd_fsid;
@@ -1950,83 +2562,100 @@ bool OSD::asok_command(string admin_command, cmdmap_t& cmdmap, string format,
     f->dump_string("state", get_state_name(get_state()));
     f->dump_unsigned("oldest_map", superblock.oldest_map);
     f->dump_unsigned("newest_map", superblock.newest_map);
-    {
-      RWLock::RLocker l(pg_map_lock);
-      f->dump_unsigned("num_pgs", pg_map.size());
-    }
+    f->dump_unsigned("num_pgs", num_pgs);
     f->close_section();
-  } else if (admin_command == "flush_journal") {
+  } else if (prefix == "flush_journal") {
     store->flush_journal();
-  } else if (admin_command == "dump_ops_in_flight" ||
-            admin_command == "ops") {
-    if (!op_tracker.dump_ops_in_flight(f)) {
-      ss << "op_tracker tracking is not enabled now, so no ops are tracked currently, even those get stuck. \
-       Please enable \"osd_enable_op_tracker\", and the tracker will start to track new ops received afterwards.";
-    }
-  } else if (admin_command == "dump_blocked_ops") {
-    if (!op_tracker.dump_ops_in_flight(f, true)) {
-      ss << "op_tracker tracking is not enabled now, so no ops are tracked currently, even those get stuck. \
-       Please enable \"osd_enable_op_tracker\", and the tracker will start to track new ops received afterwards.";
-    }
-  } else if (admin_command == "dump_historic_ops") {
-    if (!op_tracker.dump_historic_ops(f, false)) {
-      ss << "op_tracker tracking is not enabled now, so no ops are tracked currently, even those get stuck. \
-       Please enable \"osd_enable_op_tracker\", and the tracker will start to track new ops received afterwards.";
-    }
-  } else if (admin_command == "dump_historic_ops_by_duration") {
-    if (!op_tracker.dump_historic_ops(f, true)) {
-      ss << "op_tracker tracking is not enabled now, so no ops are tracked currently, even those get stuck. \
-       Please enable \"osd_enable_op_tracker\", and the tracker will start to track new ops received afterwards.";
-    }
-  } else if (admin_command == "dump_historic_slow_ops") {
-    if (!op_tracker.dump_historic_slow_ops(f)) {
-      ss << "op_tracker tracking is not enabled now, so no ops are tracked currently, even those get stuck. \
-       Please enable \"osd_enable_op_tracker\", and the tracker will start to track new ops received afterwards.";
-    }
-  } else if (admin_command == "dump_op_pq_state") {
+  } else if (prefix == "dump_ops_in_flight" ||
+             prefix == "ops" ||
+             prefix == "dump_blocked_ops" ||
+             prefix == "dump_historic_ops" ||
+             prefix == "dump_historic_ops_by_duration" ||
+             prefix == "dump_historic_slow_ops") {
+
+    const string error_str = "op_tracker tracking is not enabled now, so no ops are tracked currently, \
+even those get stuck. Please enable \"osd_enable_op_tracker\", and the tracker \
+will start to track new ops received afterwards.";
+
+    set<string> filters;
+    vector<string> filter_str;
+    if (cmd_getval(cmdmap, "filterstr", filter_str)) {
+        copy(filter_str.begin(), filter_str.end(),
+           inserter(filters, filters.end()));
+    }
+
+    if (prefix == "dump_ops_in_flight" ||
+        prefix == "ops") {
+      if (!op_tracker.dump_ops_in_flight(f, false, filters)) {
+        ss << error_str;
+       ret = -EINVAL;
+       goto out;
+      }
+    }
+    if (prefix == "dump_blocked_ops") {
+      if (!op_tracker.dump_ops_in_flight(f, true, filters)) {
+        ss << error_str;
+       ret = -EINVAL;
+       goto out;
+      }
+    }
+    if (prefix == "dump_historic_ops") {
+      if (!op_tracker.dump_historic_ops(f, false, filters)) {
+        ss << error_str;
+       ret = -EINVAL;
+       goto out;
+      }
+    }
+    if (prefix == "dump_historic_ops_by_duration") {
+      if (!op_tracker.dump_historic_ops(f, true, filters)) {
+        ss << error_str;
+       ret = -EINVAL;
+       goto out;
+      }
+    }
+    if (prefix == "dump_historic_slow_ops") {
+      if (!op_tracker.dump_historic_slow_ops(f, filters)) {
+        ss << error_str;
+       ret = -EINVAL;
+       goto out;
+      }
+    }
+  } else if (prefix == "dump_op_pq_state") {
     f->open_object_section("pq");
     op_shardedwq.dump(f);
     f->close_section();
-  } else if (admin_command == "dump_blacklist") {
+  } else if (prefix == "dump_blocklist") {
     list<pair<entity_addr_t,utime_t> > bl;
     OSDMapRef curmap = service.get_osdmap();
 
-    f->open_array_section("blacklist");
-    curmap->get_blacklist(&bl);
+    f->open_array_section("blocklist");
+    curmap->get_blocklist(&bl);
     for (list<pair<entity_addr_t,utime_t> >::iterator it = bl.begin();
        it != bl.end(); ++it) {
-      f->open_array_section("entry");
+      f->open_object_section("entry");
       f->open_object_section("entity_addr_t");
       it->first.dump(f);
       f->close_section(); //entity_addr_t
       it->second.localtime(f->dump_stream("expire_time"));
       f->close_section(); //entry
     }
-    f->close_section(); //blacklist
-  } else if (admin_command == "dump_watchers") {
+    f->close_section(); //blocklist
+  } else if (prefix == "dump_watchers") {
     list<obj_watch_item_t> watchers;
     // scan pg's
-    {
-      Mutex::Locker l(osd_lock);
-      RWLock::RLocker l2(pg_map_lock);
-      for (ceph::unordered_map<spg_t,PG*>::iterator it = pg_map.begin();
-          it != pg_map.end();
-          ++it) {
-
-        list<obj_watch_item_t> pg_watchers;
-        PG *pg = it->second;
-        pg->lock();
-        pg->get_watchers(pg_watchers);
-        pg->unlock();
-        watchers.splice(watchers.end(), pg_watchers);
-      }
+    vector<PGRef> pgs;
+    _get_pgs(&pgs);
+    for (auto& pg : pgs) {
+      list<obj_watch_item_t> pg_watchers;
+      pg->get_watchers(&pg_watchers);
+      watchers.splice(watchers.end(), pg_watchers);
     }
 
     f->open_array_section("watchers");
     for (list<obj_watch_item_t>::iterator it = watchers.begin();
        it != watchers.end(); ++it) {
 
-      f->open_array_section("watch");
+      f->open_object_section("watch");
 
       f->dump_string("namespace", it->obj.nspace);
       f->dump_string("object", it->obj.oid.name);
@@ -2035,8 +2664,8 @@ bool OSD::asok_command(string admin_command, cmdmap_t& cmdmap, string format,
       it->wi.name.dump(f);
       f->close_section(); //entity_name_t
 
-      f->dump_int("cookie", it->wi.cookie);
-      f->dump_int("timeout", it->wi.timeout_seconds);
+      f->dump_unsigned("cookie", it->wi.cookie);
+      f->dump_unsigned("timeout", it->wi.timeout_seconds);
 
       f->open_object_section("entity_addr_t");
       it->wi.addr.dump(f);
@@ -2046,7 +2675,7 @@ bool OSD::asok_command(string admin_command, cmdmap_t& cmdmap, string format,
     }
 
     f->close_section(); //watchers
-  } else if (admin_command == "dump_reservations") {
+  } else if (prefix == "dump_recovery_reservations") {
     f->open_object_section("reservations");
     f->open_object_section("local_reservations");
     service.local_reserver.dump(f);
@@ -2055,25 +2684,21 @@ bool OSD::asok_command(string admin_command, cmdmap_t& cmdmap, string format,
     service.remote_reserver.dump(f);
     f->close_section();
     f->close_section();
-  } else if (admin_command == "get_latest_osdmap") {
-    get_latest_osdmap();
-  } else if (admin_command == "heap") {
-    auto result = ceph::osd_cmds::heap(*cct, cmdmap, *f, ss);
-
-    // Note: Failed heap profile commands won't necessarily trigger an error:
-    f->open_object_section("result");
-    f->dump_string("error", cpp_strerror(result));
-    f->dump_bool("success", result >= 0);
+  } else if (prefix == "dump_scrub_reservations") {
+    f->open_object_section("scrub_reservations");
+    service.dump_scrub_reservations(f);
     f->close_section();
-  } else if (admin_command == "set_heap_property") {
+  } else if (prefix == "get_latest_osdmap") {
+    get_latest_osdmap();
+  } else if (prefix == "set_heap_property") {
     string property;
     int64_t value = 0;
     string error;
     bool success = false;
-    if (!cmd_getval(cct, cmdmap, "property", property)) {
+    if (!cmd_getval(cmdmap, "property", property)) {
       error = "unable to get property";
       success = false;
-    } else if (!cmd_getval(cct, cmdmap, "value", value)) {
+    } else if (!cmd_getval(cmdmap, "value", value)) {
       error = "unable to get value";
       success = false;
     } else if (value < 0) {
@@ -2089,12 +2714,12 @@ bool OSD::asok_command(string admin_command, cmdmap_t& cmdmap, string format,
     f->dump_string("error", error);
     f->dump_bool("success", success);
     f->close_section();
-  } else if (admin_command == "get_heap_property") {
+  } else if (prefix == "get_heap_property") {
     string property;
     size_t value = 0;
     string error;
     bool success = false;
-    if (!cmd_getval(cct, cmdmap, "property", property)) {
+    if (!cmd_getval(cmdmap, "property", property)) {
       error = "unable to get property";
       success = false;
     } else if (!ceph_heap_get_numeric_property(property.c_str(), &value)) {
@@ -2108,34 +2733,489 @@ bool OSD::asok_command(string admin_command, cmdmap_t& cmdmap, string format,
     f->dump_bool("success", success);
     f->dump_int("value", value);
     f->close_section();
-  } else if (admin_command == "dump_objectstore_kv_stats") {
+  } else if (prefix == "dump_objectstore_kv_stats") {
     store->get_db_statistics(f);
-  } else if (admin_command == "dump_scrubs") {
+  } else if (prefix == "dump_scrubs") {
     service.dumps_scrub(f);
-  } else if (admin_command == "calc_objectstore_db_histogram") {
+  } else if (prefix == "calc_objectstore_db_histogram") {
     store->generate_db_histogram(f);
-  } else if (admin_command == "flush_store_cache") {
-    store->flush_cache();
-  } else if (admin_command == "dump_pgstate_history") {
+  } else if (prefix == "flush_store_cache") {
+    store->flush_cache(&ss);
+  } else if (prefix == "dump_pgstate_history") {
     f->open_object_section("pgstate_history");
-    RWLock::RLocker l2(pg_map_lock);
-    for (ceph::unordered_map<spg_t,PG*>::iterator it = pg_map.begin();
-        it != pg_map.end();
-        ++it) {
-
-      PG *pg = it->second;
-      f->dump_stream("pg") << pg->get_pgid();
-      pg->lock();
-      pg->pgstate_history.dump(f);
-      pg->unlock();
+    f->open_array_section("pgs");
+    vector<PGRef> pgs;
+    _get_pgs(&pgs);
+    for (auto& pg : pgs) {
+      f->open_object_section("pg");
+      f->dump_stream("pg") << pg->pg_id;
+      f->dump_string("currently", pg->get_current_state());
+      pg->dump_pgstate_history(f);
+      f->close_section();
     }
     f->close_section();
-  } else {
-    assert(0 == "broken asok registration");
+    f->close_section();
+  } else if (prefix == "compact") {
+    dout(1) << "triggering manual compaction" << dendl;
+    auto start = ceph::coarse_mono_clock::now();
+    store->compact();
+    auto end = ceph::coarse_mono_clock::now();
+    double duration = std::chrono::duration<double>(end-start).count();
+    dout(1) << "finished manual compaction in "
+            << duration
+            << " seconds" << dendl;
+    f->open_object_section("compact_result");
+    f->dump_float("elapsed_time", duration);
+    f->close_section();
+  } else if (prefix == "get_mapped_pools") {
+    f->open_array_section("mapped_pools");
+    set<int64_t> poollist = get_mapped_pools();
+    for (auto pool : poollist) {
+      f->dump_int("pool_id", pool);
+    }
+    f->close_section();
+  } else if (prefix == "smart") {
+    string devid;
+    cmd_getval(cmdmap, "devid", devid);
+    ostringstream out;
+    probe_smart(devid, out);
+    outbl.append(out.str());
+  } else if (prefix == "list_devices") {
+    set<string> devnames;
+    store->get_devices(&devnames);
+    f->open_array_section("list_devices");
+    for (auto dev : devnames) {
+      if (dev.find("dm-") == 0) {
+       continue;
+      }
+      string err;
+      f->open_object_section("device");
+      f->dump_string("device", "/dev/" + dev);
+      f->dump_string("device_id", get_device_id(dev, &err));
+      f->close_section();
+    }
+    f->close_section();
+  } else if (prefix == "send_beacon") {
+    lock_guard l(osd_lock);
+    if (is_active()) {
+      send_beacon(ceph::coarse_mono_clock::now());
+    }
   }
-  f->flush(ss);
-  delete f;
-  return true;
+
+  else if (prefix == "cluster_log") {
+    vector<string> msg;
+    cmd_getval(cmdmap, "message", msg);
+    if (msg.empty()) {
+      ret = -EINVAL;
+      ss << "ignoring empty log message";
+      goto out;
+    }
+    string message = msg.front();
+    for (vector<string>::iterator a = ++msg.begin(); a != msg.end(); ++a)
+      message += " " + *a;
+    string lvl;
+    cmd_getval(cmdmap, "level", lvl);
+    clog_type level = string_to_clog_type(lvl);
+    if (level < 0) {
+      ret = -EINVAL;
+      ss << "unknown level '" << lvl << "'";
+      goto out;
+    }
+    clog->do_log(level, message);
+  }
+
+  else if (prefix == "bench") {
+    int64_t count;
+    int64_t bsize;
+    int64_t osize, onum;
+    // default count 1G, size 4MB
+    cmd_getval(cmdmap, "count", count, (int64_t)1 << 30);
+    cmd_getval(cmdmap, "size", bsize, (int64_t)4 << 20);
+    cmd_getval(cmdmap, "object_size", osize, (int64_t)0);
+    cmd_getval(cmdmap, "object_num", onum, (int64_t)0);
+
+    uint32_t duration = cct->_conf->osd_bench_duration;
+
+    if (bsize > (int64_t) cct->_conf->osd_bench_max_block_size) {
+      // let us limit the block size because the next checks rely on it
+      // having a sane value.  If we allow any block size to be set things
+      // can still go sideways.
+      ss << "block 'size' values are capped at "
+         << byte_u_t(cct->_conf->osd_bench_max_block_size) << ". If you wish to use"
+         << " a higher value, please adjust 'osd_bench_max_block_size'";
+      ret = -EINVAL;
+      goto out;
+    } else if (bsize < (int64_t) (1 << 20)) {
+      // entering the realm of small block sizes.
+      // limit the count to a sane value, assuming a configurable amount of
+      // IOPS and duration, so that the OSD doesn't get hung up on this,
+      // preventing timeouts from going off
+      int64_t max_count =
+        bsize * duration * cct->_conf->osd_bench_small_size_max_iops;
+      if (count > max_count) {
+        ss << "'count' values greater than " << max_count
+           << " for a block size of " << byte_u_t(bsize) << ", assuming "
+           << cct->_conf->osd_bench_small_size_max_iops << " IOPS,"
+           << " for " << duration << " seconds,"
+           << " can cause ill effects on osd. "
+           << " Please adjust 'osd_bench_small_size_max_iops' with a higher"
+           << " value if you wish to use a higher 'count'.";
+        ret = -EINVAL;
+        goto out;
+      }
+    } else {
+      // 1MB block sizes are big enough so that we get more stuff done.
+      // However, to avoid the osd from getting hung on this and having
+      // timers being triggered, we are going to limit the count assuming
+      // a configurable throughput and duration.
+      // NOTE: max_count is the total amount of bytes that we believe we
+      //       will be able to write during 'duration' for the given
+      //       throughput.  The block size hardly impacts this unless it's
+      //       way too big.  Given we already check how big the block size
+      //       is, it's safe to assume everything will check out.
+      int64_t max_count =
+        cct->_conf->osd_bench_large_size_max_throughput * duration;
+      if (count > max_count) {
+        ss << "'count' values greater than " << max_count
+           << " for a block size of " << byte_u_t(bsize) << ", assuming "
+           << byte_u_t(cct->_conf->osd_bench_large_size_max_throughput) << "/s,"
+           << " for " << duration << " seconds,"
+           << " can cause ill effects on osd. "
+           << " Please adjust 'osd_bench_large_size_max_throughput'"
+           << " with a higher value if you wish to use a higher 'count'.";
+        ret = -EINVAL;
+        goto out;
+      }
+    }
+
+    if (osize && bsize > osize)
+      bsize = osize;
+
+    dout(1) << " bench count " << count
+            << " bsize " << byte_u_t(bsize) << dendl;
+
+    ObjectStore::Transaction cleanupt;
+
+    if (osize && onum) {
+      bufferlist bl;
+      bufferptr bp(osize);
+      bp.zero();
+      bl.push_back(std::move(bp));
+      bl.rebuild_page_aligned();
+      for (int i=0; i<onum; ++i) {
+       char nm[30];
+       snprintf(nm, sizeof(nm), "disk_bw_test_%d", i);
+       object_t oid(nm);
+       hobject_t soid(sobject_t(oid, 0));
+       ObjectStore::Transaction t;
+       t.write(coll_t(), ghobject_t(soid), 0, osize, bl);
+       store->queue_transaction(service.meta_ch, std::move(t), NULL);
+       cleanupt.remove(coll_t(), ghobject_t(soid));
+      }
+    }
+
+    bufferlist bl;
+    bufferptr bp(bsize);
+    bp.zero();
+    bl.push_back(std::move(bp));
+    bl.rebuild_page_aligned();
+
+    {
+      C_SaferCond waiter;
+      if (!service.meta_ch->flush_commit(&waiter)) {
+       waiter.wait();
+      }
+    }
+
+    utime_t start = ceph_clock_now();
+    for (int64_t pos = 0; pos < count; pos += bsize) {
+      char nm[30];
+      unsigned offset = 0;
+      if (onum && osize) {
+       snprintf(nm, sizeof(nm), "disk_bw_test_%d", (int)(rand() % onum));
+       offset = rand() % (osize / bsize) * bsize;
+      } else {
+       snprintf(nm, sizeof(nm), "disk_bw_test_%lld", (long long)pos);
+      }
+      object_t oid(nm);
+      hobject_t soid(sobject_t(oid, 0));
+      ObjectStore::Transaction t;
+      t.write(coll_t::meta(), ghobject_t(soid), offset, bsize, bl);
+      store->queue_transaction(service.meta_ch, std::move(t), NULL);
+      if (!onum || !osize)
+       cleanupt.remove(coll_t::meta(), ghobject_t(soid));
+    }
+
+    {
+      C_SaferCond waiter;
+      if (!service.meta_ch->flush_commit(&waiter)) {
+       waiter.wait();
+      }
+    }
+    utime_t end = ceph_clock_now();
+
+    // clean up
+    store->queue_transaction(service.meta_ch, std::move(cleanupt), NULL);
+    {
+      C_SaferCond waiter;
+      if (!service.meta_ch->flush_commit(&waiter)) {
+       waiter.wait();
+      }
+    }
+
+    double elapsed = end - start;
+    double rate = count / elapsed;
+    double iops = rate / bsize;
+    f->open_object_section("osd_bench_results");
+    f->dump_int("bytes_written", count);
+    f->dump_int("blocksize", bsize);
+    f->dump_float("elapsed_sec", elapsed);
+    f->dump_float("bytes_per_sec", rate);
+    f->dump_float("iops", iops);
+    f->close_section();
+  }
+
+  else if (prefix == "flush_pg_stats") {
+    mgrc.send_pgstats();
+    f->dump_unsigned("stat_seq", service.get_osd_stat_seq());
+  }
+
+  else if (prefix == "heap") {
+    ret = ceph::osd_cmds::heap(*cct, cmdmap, *f, ss);
+  }
+
+  else if (prefix == "debug dump_missing") {
+    f->open_array_section("pgs");
+    vector<PGRef> pgs;
+    _get_pgs(&pgs);
+    for (auto& pg : pgs) {
+      string s = stringify(pg->pg_id);
+      f->open_array_section(s.c_str());
+      pg->lock();
+      pg->dump_missing(f);
+      pg->unlock();
+      f->close_section();
+    }
+    f->close_section();
+  }
+
+  else if (prefix == "debug kick_recovery_wq") {
+    int64_t delay;
+    cmd_getval(cmdmap, "delay", delay);
+    ostringstream oss;
+    oss << delay;
+    ret = cct->_conf.set_val("osd_recovery_delay_start", oss.str().c_str());
+    if (ret != 0) {
+      ss << "kick_recovery_wq: error setting "
+        << "osd_recovery_delay_start to '" << delay << "': error "
+        << ret;
+      goto out;
+    }
+    cct->_conf.apply_changes(nullptr);
+    ss << "kicking recovery queue. set osd_recovery_delay_start "
+       << "to " << cct->_conf->osd_recovery_delay_start;
+  }
+
+  else if (prefix == "cpu_profiler") {
+    ostringstream ds;
+    string arg;
+    cmd_getval(cmdmap, "arg", arg);
+    vector<string> argvec;
+    get_str_vec(arg, argvec);
+    cpu_profiler_handle_command(argvec, ds);
+    outbl.append(ds.str());
+  }
+
+  else if (prefix == "dump_pg_recovery_stats") {
+    lock_guard l(osd_lock);
+    pg_recovery_stats.dump_formatted(f);
+  }
+
+  else if (prefix == "reset_pg_recovery_stats") {
+    lock_guard l(osd_lock);
+    pg_recovery_stats.reset();
+  }
+
+  else if (prefix == "perf histogram dump") {
+    std::string logger;
+    std::string counter;
+    cmd_getval(cmdmap, "logger", logger);
+    cmd_getval(cmdmap, "counter", counter);
+    cct->get_perfcounters_collection()->dump_formatted_histograms(
+      f, false, logger, counter);
+  }
+
+  else if (prefix == "cache drop") {
+    lock_guard l(osd_lock);
+    dout(20) << "clearing all caches" << dendl;
+    // Clear the objectstore's cache - onode and buffer for Bluestore,
+    // system's pagecache for Filestore
+    ret = store->flush_cache(&ss);
+    if (ret < 0) {
+      ss << "Error flushing objectstore cache: " << cpp_strerror(ret);
+      goto out;
+    }
+    // Clear the objectcontext cache (per PG)
+    vector<PGRef> pgs;
+    _get_pgs(&pgs);
+    for (auto& pg: pgs) {
+      pg->clear_cache();
+    }
+  }
+
+  else if (prefix == "cache status") {
+    lock_guard l(osd_lock);
+    int obj_ctx_count = 0;
+    vector<PGRef> pgs;
+    _get_pgs(&pgs);
+    for (auto& pg: pgs) {
+      obj_ctx_count += pg->get_cache_obj_count();
+    }
+    f->open_object_section("cache_status");
+    f->dump_int("object_ctx", obj_ctx_count);
+    store->dump_cache_stats(f);
+    f->close_section();
+  }
+
+  else if (prefix == "scrub_purged_snaps") {
+    lock_guard l(osd_lock);
+    scrub_purged_snaps();
+  }
+
+  else if (prefix == "dump_osd_network") {
+    lock_guard l(osd_lock);
+    int64_t value = 0;
+    if (!(cmd_getval(cmdmap, "value", value))) {
+      // Convert milliseconds to microseconds
+      value = static_cast<double>(g_conf().get_val<double>(
+                                   "mon_warn_on_slow_ping_time")) * 1000;
+      if (value == 0) {
+       double ratio = g_conf().get_val<double>("mon_warn_on_slow_ping_ratio");
+       value = g_conf().get_val<int64_t>("osd_heartbeat_grace");
+       value *= 1000000 * ratio; // Seconds of grace to microseconds at ratio
+      }
+    } else {
+      // Convert user input to microseconds
+      value *= 1000;
+    }
+    if (value < 0) value = 0;
+
+    struct osd_ping_time_t {
+      uint32_t pingtime;
+      int to;
+      bool back;
+      std::array<uint32_t,3> times;
+      std::array<uint32_t,3> min;
+      std::array<uint32_t,3> max;
+      uint32_t last;
+      uint32_t last_update;
+
+      bool operator<(const osd_ping_time_t& rhs) const {
+       if (pingtime < rhs.pingtime)
+          return true;
+       if (pingtime > rhs.pingtime)
+         return false;
+        if (to < rhs.to)
+         return true;
+        if (to > rhs.to)
+         return false;
+       return back;
+      }
+    };
+
+    set<osd_ping_time_t> sorted;
+    // Get pingtimes under lock and not on the stack
+    map<int, osd_stat_t::Interfaces> *pingtimes = new map<int, osd_stat_t::Interfaces>;
+    service.get_hb_pingtime(pingtimes);
+    for (auto j : *pingtimes) {
+      if (j.second.last_update == 0)
+       continue;
+      osd_ping_time_t item;
+      item.pingtime = std::max(j.second.back_pingtime[0], j.second.back_pingtime[1]);
+      item.pingtime = std::max(item.pingtime, j.second.back_pingtime[2]);
+      if (item.pingtime >= value) {
+       item.to = j.first;
+       item.times[0] = j.second.back_pingtime[0];
+       item.times[1] = j.second.back_pingtime[1];
+       item.times[2] = j.second.back_pingtime[2];
+       item.min[0] = j.second.back_min[0];
+       item.min[1] = j.second.back_min[1];
+       item.min[2] = j.second.back_min[2];
+       item.max[0] = j.second.back_max[0];
+       item.max[1] = j.second.back_max[1];
+       item.max[2] = j.second.back_max[2];
+       item.last = j.second.back_last;
+       item.back = true;
+       item.last_update = j.second.last_update;
+       sorted.emplace(item);
+      }
+      if (j.second.front_last == 0)
+       continue;
+      item.pingtime = std::max(j.second.front_pingtime[0], j.second.front_pingtime[1]);
+      item.pingtime = std::max(item.pingtime, j.second.front_pingtime[2]);
+      if (item.pingtime >= value) {
+       item.to = j.first;
+       item.times[0] = j.second.front_pingtime[0];
+       item.times[1] = j.second.front_pingtime[1];
+       item.times[2] = j.second.front_pingtime[2];
+       item.min[0] = j.second.front_min[0];
+       item.min[1] = j.second.front_min[1];
+       item.min[2] = j.second.front_min[2];
+       item.max[0] = j.second.front_max[0];
+       item.max[1] = j.second.front_max[1];
+       item.max[2] = j.second.front_max[2];
+       item.last = j.second.front_last;
+       item.last_update = j.second.last_update;
+       item.back = false;
+       sorted.emplace(item);
+      }
+    }
+    delete pingtimes;
+    //
+    // Network ping times (1min 5min 15min)
+    f->open_object_section("network_ping_times");
+    f->dump_int("threshold", value / 1000);
+    f->open_array_section("entries");
+    for (auto &sitem : boost::adaptors::reverse(sorted)) {
+      ceph_assert(sitem.pingtime >= value);
+      f->open_object_section("entry");
+
+      const time_t lu(sitem.last_update);
+      char buffer[26];
+      string lustr(ctime_r(&lu, buffer));
+      lustr.pop_back();   // Remove trailing \n
+      auto stale = cct->_conf.get_val<int64_t>("osd_heartbeat_stale");
+      f->dump_string("last update", lustr);
+      f->dump_bool("stale", ceph_clock_now().sec() - sitem.last_update > stale);
+      f->dump_int("from osd", whoami);
+      f->dump_int("to osd", sitem.to);
+      f->dump_string("interface", (sitem.back ? "back" : "front"));
+      f->open_object_section("average");
+      f->dump_format_unquoted("1min", "%s", fixed_u_to_string(sitem.times[0],3).c_str());
+      f->dump_format_unquoted("5min", "%s", fixed_u_to_string(sitem.times[1],3).c_str());
+      f->dump_format_unquoted("15min", "%s", fixed_u_to_string(sitem.times[2],3).c_str());
+      f->close_section();  // average
+      f->open_object_section("min");
+      f->dump_format_unquoted("1min", "%s", fixed_u_to_string(sitem.max[0],3).c_str());
+      f->dump_format_unquoted("5min", "%s", fixed_u_to_string(sitem.max[1],3).c_str());
+      f->dump_format_unquoted("15min", "%s", fixed_u_to_string(sitem.max[2],3).c_str());
+      f->close_section();  // min
+      f->open_object_section("max");
+      f->dump_format_unquoted("1min", "%s", fixed_u_to_string(sitem.max[0],3).c_str());
+      f->dump_format_unquoted("5min", "%s", fixed_u_to_string(sitem.max[1],3).c_str());
+      f->dump_format_unquoted("15min", "%s", fixed_u_to_string(sitem.max[2],3).c_str());
+      f->close_section();  // max
+      f->dump_format_unquoted("last", "%s", fixed_u_to_string(sitem.last,3).c_str());
+      f->close_section();  // entry
+    }
+    f->close_section(); // entries
+    f->close_section(); // network_ping_times
+  } else {
+    ceph_abort_msg("broken asok registration");
+  }
+
+ out:
+  on_finish(ret, ss.str(), outbl);
 }
 
 class TestOpsSocketHook : public AdminSocketHook {
@@ -2143,15 +3223,23 @@ class TestOpsSocketHook : public AdminSocketHook {
   ObjectStore *store;
 public:
   TestOpsSocketHook(OSDService *s, ObjectStore *st) : service(s), store(st) {}
-  bool call(std::string command, cmdmap_t& cmdmap, std::string format,
-           bufferlist& out) override {
-    stringstream ss;
-    test_ops(service, store, command, cmdmap, ss);
-    out.append(ss);
-    return true;
+  int call(std::string_view command, const cmdmap_t& cmdmap,
+          Formatter *f,
+          std::ostream& errss,
+          bufferlist& out) override {
+    int r = 0;
+    stringstream outss;
+    try {
+      test_ops(service, store, command, cmdmap, outss);
+      out.append(outss);
+    } catch (const TOPNSPC::common::bad_cmd_get& e) {
+      errss << e.what();
+      r = -EINVAL;
+    }
+    return r;
   }
   void test_ops(OSDService *service, ObjectStore *store,
-               const std::string &command, cmdmap_t& cmdmap, ostream &ss);
+               std::string_view command, const cmdmap_t& cmdmap, ostream &ss);
 
 };
 
@@ -2184,10 +3272,10 @@ int OSD::enable_disable_fuse(bool stop)
     delete fuse_store;
     fuse_store = NULL;
     r = ::rmdir(mntpath.c_str());
-    if (r < 0)
-      r = -errno;
     if (r < 0) {
-      derr << __func__ << " failed to rmdir " << mntpath << dendl;
+      r = -errno;
+      derr << __func__ << " failed to rmdir " << mntpath << ": "
+           << cpp_strerror(r) << dendl;
       return r;
     }
     return 0;
@@ -2215,34 +3303,122 @@ int OSD::enable_disable_fuse(bool stop)
   return 0;
 }
 
+size_t OSD::get_num_cache_shards()
+{
+  return cct->_conf.get_val<Option::size_t>("osd_num_cache_shards");
+}
+
+int OSD::get_num_op_shards()
+{
+  if (cct->_conf->osd_op_num_shards)
+    return cct->_conf->osd_op_num_shards;
+  if (store_is_rotational)
+    return cct->_conf->osd_op_num_shards_hdd;
+  else
+    return cct->_conf->osd_op_num_shards_ssd;
+}
+
+int OSD::get_num_op_threads()
+{
+  if (cct->_conf->osd_op_num_threads_per_shard)
+    return get_num_op_shards() * cct->_conf->osd_op_num_threads_per_shard;
+  if (store_is_rotational)
+    return get_num_op_shards() * cct->_conf->osd_op_num_threads_per_shard_hdd;
+  else
+    return get_num_op_shards() * cct->_conf->osd_op_num_threads_per_shard_ssd;
+}
+
+float OSD::get_osd_recovery_sleep()
+{
+  if (cct->_conf->osd_recovery_sleep)
+    return cct->_conf->osd_recovery_sleep;
+  if (!store_is_rotational && !journal_is_rotational)
+    return cct->_conf->osd_recovery_sleep_ssd;
+  else if (store_is_rotational && !journal_is_rotational)
+    return cct->_conf.get_val<double>("osd_recovery_sleep_hybrid");
+  else
+    return cct->_conf->osd_recovery_sleep_hdd;
+}
+
+float OSD::get_osd_delete_sleep()
+{
+  float osd_delete_sleep = cct->_conf.get_val<double>("osd_delete_sleep");
+  if (osd_delete_sleep > 0)
+    return osd_delete_sleep;
+  if (!store_is_rotational && !journal_is_rotational)
+    return cct->_conf.get_val<double>("osd_delete_sleep_ssd");
+  if (store_is_rotational && !journal_is_rotational)
+    return cct->_conf.get_val<double>("osd_delete_sleep_hybrid");
+  return cct->_conf.get_val<double>("osd_delete_sleep_hdd");
+}
+
+int OSD::get_recovery_max_active()
+{
+  if (cct->_conf->osd_recovery_max_active)
+    return cct->_conf->osd_recovery_max_active;
+  if (store_is_rotational)
+    return cct->_conf->osd_recovery_max_active_hdd;
+  else
+    return cct->_conf->osd_recovery_max_active_ssd;
+}
+
+float OSD::get_osd_snap_trim_sleep()
+{
+  float osd_snap_trim_sleep = cct->_conf.get_val<double>("osd_snap_trim_sleep");
+  if (osd_snap_trim_sleep > 0)
+    return osd_snap_trim_sleep;
+  if (!store_is_rotational && !journal_is_rotational)
+    return cct->_conf.get_val<double>("osd_snap_trim_sleep_ssd");
+  if (store_is_rotational && !journal_is_rotational)
+    return cct->_conf.get_val<double>("osd_snap_trim_sleep_hybrid");
+  return cct->_conf.get_val<double>("osd_snap_trim_sleep_hdd");
+}
+
 int OSD::init()
 {
+  OSDMapRef osdmap;
   CompatSet initial, diff;
-  Mutex::Locker lock(osd_lock);
+  std::lock_guard lock(osd_lock);
   if (is_stopping())
     return 0;
 
   tick_timer.init();
   tick_timer_without_osd_lock.init();
   service.recovery_request_timer.init();
+  service.sleep_timer.init();
+
+  boot_finisher.start();
+
+  {
+    string val;
+    store->read_meta("require_osd_release", &val);
+    last_require_osd_release = ceph_release_from_name(val);
+  }
 
   // mount.
-  dout(2) << "mounting " << dev_path << " "
-         << (journal_path.empty() ? "(no journal)" : journal_path) << dendl;
-  assert(store);  // call pre_init() first!
+  dout(2) << "init " << dev_path
+         << " (looks like " << (store_is_rotational ? "hdd" : "ssd") << ")"
+         << dendl;
+  dout(2) << "journal " << journal_path << dendl;
+  ceph_assert(store);  // call pre_init() first!
 
-  store->set_cache_shards(cct->_conf->osd_op_num_shards);
+  store->set_cache_shards(get_num_cache_shards());
 
   int r = store->mount();
   if (r < 0) {
     derr << "OSD:init: unable to mount object store" << dendl;
     return r;
   }
+  journal_is_rotational = store->is_journal_rotational();
+  dout(2) << "journal looks like " << (journal_is_rotational ? "hdd" : "ssd")
+          << dendl;
 
   enable_disable_fuse(false);
 
   dout(2) << "boot" << dendl;
 
+  service.meta_ch = store->open_collection(coll_t::meta());
+
   // initialize the daily loadavg with current 15min loadavg
   double loadavgs[3];
   if (getloadavg(loadavgs, 3) == 3) {
@@ -2253,6 +3429,8 @@ int OSD::init()
   }
 
   int rotating_auth_attempts = 0;
+  auto rotating_auth_timeout =
+    g_conf().get_val<int64_t>("rotating_keys_bootstrap_timeout");
 
   // sanity check long object name handling
   {
@@ -2314,49 +3492,88 @@ int OSD::init()
     goto out;
   }
 
-  initial = get_osd_initial_compat_set();
-  diff = superblock.compat_features.unsupported(initial);
-  if (superblock.compat_features.merge(initial)) {
-    // We need to persist the new compat_set before we
-    // do anything else
-    dout(5) << "Upgrading superblock adding: " << diff << dendl;
+  startup_time = ceph::mono_clock::now();
+
+  // load up "current" osdmap
+  assert_warn(!get_osdmap());
+  if (get_osdmap()) {
+    derr << "OSD::init: unable to read current osdmap" << dendl;
+    r = -EINVAL;
+    goto out;
+  }
+  osdmap = get_map(superblock.current_epoch);
+  set_osdmap(osdmap);
+
+  // make sure we don't have legacy pgs deleting
+  {
+    vector<coll_t> ls;
+    int r = store->list_collections(ls);
+    ceph_assert(r >= 0);
+    for (auto c : ls) {
+      spg_t pgid;
+      if (c.is_pg(&pgid) &&
+         !osdmap->have_pg_pool(pgid.pool())) {
+       ghobject_t oid = make_final_pool_info_oid(pgid.pool());
+       if (!store->exists(service.meta_ch, oid)) {
+         derr << __func__ << " missing pg_pool_t for deleted pool "
+              << pgid.pool() << " for pg " << pgid
+              << "; please downgrade to luminous and allow "
+              << "pg deletion to complete before upgrading" << dendl;
+         ceph_abort();
+       }
+      }
+    }
+  }
+
+  initial = get_osd_initial_compat_set();
+  diff = superblock.compat_features.unsupported(initial);
+  if (superblock.compat_features.merge(initial)) {
+    // Are we adding SNAPMAPPER2?
+    if (diff.incompat.contains(CEPH_OSD_FEATURE_INCOMPAT_SNAPMAPPER2)) {
+      dout(1) << __func__ << " upgrade snap_mapper (first start as octopus)"
+             << dendl;
+      auto ch = service.meta_ch;
+      auto hoid = make_snapmapper_oid();
+      unsigned max = cct->_conf->osd_target_transaction_size;
+      r = SnapMapper::convert_legacy(cct, store, ch, hoid, max);
+      if (r < 0)
+       goto out;
+    }
+    // We need to persist the new compat_set before we
+    // do anything else
+    dout(5) << "Upgrading superblock adding: " << diff << dendl;
     ObjectStore::Transaction t;
     write_superblock(t);
-    r = store->apply_transaction(service.meta_osr.get(), std::move(t));
+    r = store->queue_transaction(service.meta_ch, std::move(t));
     if (r < 0)
       goto out;
   }
 
   // make sure snap mapper object exists
-  if (!store->exists(coll_t::meta(), OSD::make_snapmapper_oid())) {
+  if (!store->exists(service.meta_ch, OSD::make_snapmapper_oid())) {
     dout(10) << "init creating/touching snapmapper object" << dendl;
     ObjectStore::Transaction t;
     t.touch(coll_t::meta(), OSD::make_snapmapper_oid());
-    r = store->apply_transaction(service.meta_osr.get(), std::move(t));
+    r = store->queue_transaction(service.meta_ch, std::move(t));
+    if (r < 0)
+      goto out;
+  }
+  if (!store->exists(service.meta_ch, OSD::make_purged_snaps_oid())) {
+    dout(10) << "init creating/touching purged_snaps object" << dendl;
+    ObjectStore::Transaction t;
+    t.touch(coll_t::meta(), OSD::make_purged_snaps_oid());
+    r = store->queue_transaction(service.meta_ch, std::move(t));
     if (r < 0)
       goto out;
   }
-
-  class_handler = new ClassHandler(cct);
-  cls_initialize(class_handler);
 
   if (cct->_conf->osd_open_classes_on_start) {
-    int r = class_handler->open_all_classes();
+    int r = ClassHandler::get_instance().open_all_classes();
     if (r)
       dout(1) << "warning: got an error loading one or more classes: " << cpp_strerror(r) << dendl;
   }
 
-  // load up "current" osdmap
-  assert_warn(!osdmap);
-  if (osdmap) {
-    derr << "OSD::init: unable to read current osdmap" << dendl;
-    r = -EINVAL;
-    goto out;
-  }
-  osdmap = get_map(superblock.current_epoch);
-  check_osdmap_features(store);
-
-  create_recoverystate_perf();
+  check_osdmap_features();
 
   {
     epoch_t bind_epoch = osdmap->get_epoch();
@@ -2365,25 +3582,47 @@ int OSD::init()
 
   clear_temp_objects();
 
+  // initialize osdmap references in sharded wq
+  for (auto& shard : shards) {
+    std::lock_guard l(shard->osdmap_lock);
+    shard->shard_osdmap = osdmap;
+  }
+
   // load up pgs (as they previously existed)
   load_pgs();
 
   dout(2) << "superblock: I am osd." << superblock.whoami << dendl;
-  dout(0) << "using " << op_queue << " op queue with priority op cut off at " <<
-    op_prio_cutoff << "." << dendl;
-
-  create_logger();
-
-  // i'm ready!
-  client_messenger->add_dispatcher_head(this);
-  cluster_messenger->add_dispatcher_head(this);
 
-  hb_front_client_messenger->add_dispatcher_head(&heartbeat_dispatcher);
-  hb_back_client_messenger->add_dispatcher_head(&heartbeat_dispatcher);
-  hb_front_server_messenger->add_dispatcher_head(&heartbeat_dispatcher);
-  hb_back_server_messenger->add_dispatcher_head(&heartbeat_dispatcher);
+  if (cct->_conf.get_val<bool>("osd_compact_on_start")) {
+    dout(2) << "compacting object store's omap" << dendl;
+    store->compact();
+  }
 
-  objecter_messenger->add_dispatcher_head(service.objecter);
+  // prime osd stats
+  {
+    struct store_statfs_t stbuf;
+    osd_alert_list_t alerts;
+    int r = store->statfs(&stbuf, &alerts);
+    ceph_assert(r == 0);
+    service.set_statfs(stbuf, alerts);
+  }
+
+  // client_messenger's auth_client will be set up by monc->init() later.
+  for (auto m : { cluster_messenger,
+       objecter_messenger,
+       hb_front_client_messenger,
+       hb_back_client_messenger,
+       hb_front_server_messenger,
+       hb_back_server_messenger } ) {
+    m->set_auth_client(monc);
+  }
+  for (auto m : { client_messenger,
+       cluster_messenger,
+       hb_front_server_messenger,
+       hb_back_server_messenger }) {
+    m->set_auth_server(monc);
+  }
+  monc->set_handle_authentication_dispatcher(this);
 
   monc->set_want_keys(CEPH_ENTITY_TYPE_MON | CEPH_ENTITY_TYPE_OSD
                       | CEPH_ENTITY_TYPE_MGR);
@@ -2391,110 +3630,116 @@ int OSD::init()
   if (r < 0)
     goto out;
 
-  /**
-   * FIXME: this is a placeholder implementation that unconditionally
-   * sends every is_primary PG's stats every time we're called, unlike
-   * the existing mon PGStats mechanism that uses pg_stat_queue and acks.
-   * This has equivalent cost to the existing worst case where all
-   * PGs are busy and their stats are always enqueued for sending.
-   */
-  mgrc.set_pgstats_cb([this](){
-      RWLock::RLocker l(map_lock);
-      
-      utime_t had_for = ceph_clock_now() - had_map_since;
-      osd_stat_t cur_stat = service.get_osd_stat();
-      cur_stat.os_perf_stat = store->get_cur_stats();
-
-      MPGStats *m = new MPGStats(monc->get_fsid(), osdmap->get_epoch(), had_for);
-      m->osd_stat = cur_stat;
-
-      Mutex::Locker lec{min_last_epoch_clean_lock};
-      min_last_epoch_clean = osdmap->get_epoch();
-      min_last_epoch_clean_pgs.clear();
-      RWLock::RLocker lpg(pg_map_lock);
-      for (const auto &i : pg_map) {
-        PG *pg = i.second;
-        if (!pg->is_primary()) {
-          continue;
-        }
-
-        pg->pg_stats_publish_lock.Lock();
-        if (pg->pg_stats_publish_valid) {
-          m->pg_stat[pg->info.pgid.pgid] = pg->pg_stats_publish;
-         const auto lec = pg->pg_stats_publish.get_effective_last_epoch_clean();
-         min_last_epoch_clean = min(min_last_epoch_clean, lec);
-         min_last_epoch_clean_pgs.push_back(pg->info.pgid.pgid);
-        }
-        pg->pg_stats_publish_lock.Unlock();
-      }
-
-      return m;
-  });
-
+  mgrc.set_pgstats_cb([this]() { return collect_pg_stats(); });
+  mgrc.set_perf_metric_query_cb(
+    [this](const ConfigPayload &config_payload) {
+        set_perf_queries(config_payload);
+      },
+      [this] {
+        return get_perf_reports();
+      });
   mgrc.init();
-  client_messenger->add_dispatcher_head(&mgrc);
 
   // tell monc about log_client so it will know about mon session resets
   monc->set_log_client(&log_client);
   update_log_config();
 
-  osd_tp.start();
-  osd_op_tp.start();
-  disk_tp.start();
-  command_tp.start();
+  // i'm ready!
+  client_messenger->add_dispatcher_tail(&mgrc);
+  client_messenger->add_dispatcher_tail(this);
+  cluster_messenger->add_dispatcher_head(this);
+
+  hb_front_client_messenger->add_dispatcher_head(&heartbeat_dispatcher);
+  hb_back_client_messenger->add_dispatcher_head(&heartbeat_dispatcher);
+  hb_front_server_messenger->add_dispatcher_head(&heartbeat_dispatcher);
+  hb_back_server_messenger->add_dispatcher_head(&heartbeat_dispatcher);
+
+  objecter_messenger->add_dispatcher_head(service.objecter.get());
 
-  set_disk_tp_priority();
+  service.init();
+  service.publish_map(osdmap);
+  service.publish_superblock(superblock);
+  service.max_oldest_map = superblock.oldest_map;
+
+  for (auto& shard : shards) {
+    // put PGs in a temporary set because we may modify pg_slots
+    // unordered_map below.
+    set<PGRef> pgs;
+    for (auto& i : shard->pg_slots) {
+      PGRef pg = i.second->pg;
+      if (!pg) {
+       continue;
+      }
+      pgs.insert(pg);
+    }
+    for (auto pg : pgs) {
+      std::scoped_lock l{*pg};
+      set<pair<spg_t,epoch_t>> new_children;
+      set<pair<spg_t,epoch_t>> merge_pgs;
+      service.identify_splits_and_merges(pg->get_osdmap(), osdmap, pg->pg_id,
+                                        &new_children, &merge_pgs);
+      if (!new_children.empty()) {
+       for (auto shard : shards) {
+         shard->prime_splits(osdmap, &new_children);
+       }
+       assert(new_children.empty());
+      }
+      if (!merge_pgs.empty()) {
+       for (auto shard : shards) {
+         shard->prime_merges(osdmap, &merge_pgs);
+       }
+       assert(merge_pgs.empty());
+      }
+    }
+  }
+
+  osd_op_tp.start();
 
   // start the heartbeat
   heartbeat_thread.create("osd_srv_heartbt");
 
   // tick
-  tick_timer.add_event_after(cct->_conf->osd_heartbeat_interval, new C_Tick(this));
+  tick_timer.add_event_after(get_tick_interval(),
+                            new C_Tick(this));
   {
-    Mutex::Locker l(tick_timer_lock);
-    tick_timer_without_osd_lock.add_event_after(cct->_conf->osd_heartbeat_interval, new C_Tick_WithoutOSDLock(this));
+    std::lock_guard l(tick_timer_lock);
+    tick_timer_without_osd_lock.add_event_after(get_tick_interval(),
+                                               new C_Tick_WithoutOSDLock(this));
   }
 
-  service.init();
-  service.publish_map(osdmap);
-  service.publish_superblock(superblock);
-  service.max_oldest_map = superblock.oldest_map;
-
-  osd_lock.Unlock();
+  osd_lock.unlock();
 
   r = monc->authenticate();
   if (r < 0) {
-    osd_lock.Lock(); // locker is going to unlock this on function exit
-    if (is_stopping())
-      r =  0;
-    goto monout;
+    derr << __func__ << " authentication failed: " << cpp_strerror(r)
+         << dendl;
+    exit(1);
   }
 
-  while (monc->wait_auth_rotating(30.0) < 0) {
+  while (monc->wait_auth_rotating(rotating_auth_timeout) < 0) {
     derr << "unable to obtain rotating service keys; retrying" << dendl;
     ++rotating_auth_attempts;
-    if (rotating_auth_attempts > g_conf->max_rotating_auth_attempts) {
-        osd_lock.Lock(); // make locker happy
-        if (!is_stopping()) {
-            r = - ETIMEDOUT;
-        }
-        goto monout;
+    if (rotating_auth_attempts > g_conf()->max_rotating_auth_attempts) {
+        derr << __func__ << " wait_auth_rotating timed out" << dendl;
+       exit(1);
     }
   }
 
   r = update_crush_device_class();
   if (r < 0) {
-    osd_lock.Lock();
-    goto monout;
+    derr << __func__ << " unable to update_crush_device_class: "
+        << cpp_strerror(r) << dendl;
+    exit(1);
   }
 
   r = update_crush_location();
   if (r < 0) {
-    osd_lock.Lock();
-    goto monout;
+    derr << __func__ << " unable to update_crush_location: "
+         << cpp_strerror(r) << dendl;
+    exit(1);
   }
 
-  osd_lock.Lock();
+  osd_lock.lock();
   if (is_stopping())
     return 0;
 
@@ -2506,7 +3751,6 @@ int OSD::init()
 
   dout(10) << "ensuring pgs have consumed prior maps" << dendl;
   consume_map();
-  peering_wq.drain();
 
   dout(0) << "done with init, starting boot process" << dendl;
 
@@ -2524,9 +3768,6 @@ int OSD::init()
   start_boot();
 
   return 0;
-monout:
-  mgrc.shutdown();
-  monc->shutdown();
 
 out:
   enable_disable_fuse(true);
@@ -2540,116 +3781,141 @@ void OSD::final_init()
 {
   AdminSocket *admin_socket = cct->get_admin_socket();
   asok_hook = new OSDSocketHook(this);
-  int r = admin_socket->register_command("status", "status", asok_hook,
+  int r = admin_socket->register_command("status", asok_hook,
                                         "high-level status of OSD");
-  assert(r == 0);
-  r = admin_socket->register_command("flush_journal", "flush_journal",
+  ceph_assert(r == 0);
+  r = admin_socket->register_command("flush_journal",
                                      asok_hook,
                                      "flush the journal to permanent store");
-  assert(r == 0);
-  r = admin_socket->register_command("dump_ops_in_flight",
-                                    "dump_ops_in_flight", asok_hook,
+  ceph_assert(r == 0);
+  r = admin_socket->register_command("dump_ops_in_flight " \
+                                    "name=filterstr,type=CephString,n=N,req=false",
+                                    asok_hook,
                                     "show the ops currently in flight");
-  assert(r == 0);
-  r = admin_socket->register_command("ops",
-                                    "ops", asok_hook,
+  ceph_assert(r == 0);
+  r = admin_socket->register_command("ops " \
+                                    "name=filterstr,type=CephString,n=N,req=false",
+                                    asok_hook,
                                     "show the ops currently in flight");
-  assert(r == 0);
-  r = admin_socket->register_command("dump_blocked_ops",
-                                    "dump_blocked_ops", asok_hook,
+  ceph_assert(r == 0);
+  r = admin_socket->register_command("dump_blocked_ops " \
+                                    "name=filterstr,type=CephString,n=N,req=false",
+                                    asok_hook,
                                     "show the blocked ops currently in flight");
-  assert(r == 0);
-  r = admin_socket->register_command("dump_historic_ops", "dump_historic_ops",
+  ceph_assert(r == 0);
+  r = admin_socket->register_command("dump_historic_ops " \
+                                     "name=filterstr,type=CephString,n=N,req=false",
                                     asok_hook,
                                     "show recent ops");
-  assert(r == 0);
-  r = admin_socket->register_command("dump_historic_slow_ops", "dump_historic_slow_ops",
+  ceph_assert(r == 0);
+  r = admin_socket->register_command("dump_historic_slow_ops " \
+                                     "name=filterstr,type=CephString,n=N,req=false",
                                     asok_hook,
                                     "show slowest recent ops");
-  assert(r == 0);
-  r = admin_socket->register_command("dump_historic_ops_by_duration", "dump_historic_ops_by_duration",
+  ceph_assert(r == 0);
+  r = admin_socket->register_command("dump_historic_ops_by_duration " \
+                                     "name=filterstr,type=CephString,n=N,req=false",
                                     asok_hook,
                                     "show slowest recent ops, sorted by duration");
-  assert(r == 0);
-  r = admin_socket->register_command("dump_op_pq_state", "dump_op_pq_state",
+  ceph_assert(r == 0);
+  r = admin_socket->register_command("dump_op_pq_state",
                                     asok_hook,
                                     "dump op priority queue state");
-  assert(r == 0);
-  r = admin_socket->register_command("dump_blacklist", "dump_blacklist",
+  ceph_assert(r == 0);
+  r = admin_socket->register_command("dump_blocklist",
                                     asok_hook,
-                                    "dump blacklisted clients and times");
-  assert(r == 0);
-  r = admin_socket->register_command("dump_watchers", "dump_watchers",
+                                    "dump blocklisted clients and times");
+  ceph_assert(r == 0);
+  r = admin_socket->register_command("dump_watchers",
                                     asok_hook,
                                     "show clients which have active watches,"
                                     " and on which objects");
-  assert(r == 0);
-  r = admin_socket->register_command("dump_reservations", "dump_reservations",
+  ceph_assert(r == 0);
+  r = admin_socket->register_command("dump_recovery_reservations",
                                     asok_hook,
                                     "show recovery reservations");
-  assert(r == 0);
-  r = admin_socket->register_command("get_latest_osdmap", "get_latest_osdmap",
+  ceph_assert(r == 0);
+  r = admin_socket->register_command("dump_scrub_reservations",
+                                    asok_hook,
+                                    "show scrub reservations");
+  ceph_assert(r == 0);
+  r = admin_socket->register_command("get_latest_osdmap",
                                     asok_hook,
                                     "force osd to update the latest map from "
                                     "the mon");
-  assert(r == 0);
-
-  r = admin_socket->register_command( "heap",
-                                      "heap " \
-                                      "name=heapcmd,type=CephString",
-                                      asok_hook,
-                                      "show heap usage info (available only if "
-                                      "compiled with tcmalloc)");
-  assert(r == 0);
-
-  r = admin_socket->register_command("set_heap_property",
-                                    "set_heap_property " \
+  ceph_assert(r == 0);
+
+  r = admin_socket->register_command("set_heap_property " \
                                     "name=property,type=CephString " \
                                     "name=value,type=CephInt",
                                     asok_hook,
                                     "update malloc extension heap property");
-  assert(r == 0);
+  ceph_assert(r == 0);
 
-  r = admin_socket->register_command("get_heap_property",
-                                    "get_heap_property " \
+  r = admin_socket->register_command("get_heap_property " \
                                     "name=property,type=CephString",
                                     asok_hook,
                                     "get malloc extension heap property");
-  assert(r == 0);
+  ceph_assert(r == 0);
 
   r = admin_socket->register_command("dump_objectstore_kv_stats",
-                                    "dump_objectstore_kv_stats",
                                     asok_hook,
                                     "print statistics of kvdb which used by bluestore");
-  assert(r == 0);
+  ceph_assert(r == 0);
 
   r = admin_socket->register_command("dump_scrubs",
-                                    "dump_scrubs",
                                     asok_hook,
                                     "print scheduled scrubs");
-  assert(r == 0);
+  ceph_assert(r == 0);
 
   r = admin_socket->register_command("calc_objectstore_db_histogram",
-                                     "calc_objectstore_db_histogram",
                                      asok_hook,
                                      "Generate key value histogram of kvdb(rocksdb) which used by bluestore");
-  assert(r == 0);
+  ceph_assert(r == 0);
 
   r = admin_socket->register_command("flush_store_cache",
-                                     "flush_store_cache",
                                      asok_hook,
                                      "Flush bluestore internal cache");
-  assert(r == 0);
-  r = admin_socket->register_command("dump_pgstate_history", "dump_pgstate_history",
+  ceph_assert(r == 0);
+  r = admin_socket->register_command("dump_pgstate_history",
                                     asok_hook,
                                     "show recent state history");
-  assert(r == 0);
+  ceph_assert(r == 0);
+
+  r = admin_socket->register_command("compact",
+                                    asok_hook,
+                                    "Commpact object store's omap."
+                                     " WARNING: Compaction probably slows your requests");
+  ceph_assert(r == 0);
+
+  r = admin_socket->register_command("get_mapped_pools",
+                                     asok_hook,
+                                     "dump pools whose PG(s) are mapped to this OSD.");
+
+  ceph_assert(r == 0);
+
+  r = admin_socket->register_command("smart name=devid,type=CephString,req=false",
+                                     asok_hook,
+                                     "probe OSD devices for SMART data.");
+
+  ceph_assert(r == 0);
+
+  r = admin_socket->register_command("list_devices",
+                                     asok_hook,
+                                     "list OSD devices.");
+  r = admin_socket->register_command("send_beacon",
+                                     asok_hook,
+                                     "send OSD beacon to mon immediately");
+
+  r = admin_socket->register_command(
+    "dump_osd_network name=value,type=CephInt,req=false", asok_hook,
+    "Dump osd heartbeat network ping times");
+  ceph_assert(r == 0);
 
   test_ops_hook = new TestOpsSocketHook(&(this->service), this->store);
   // Note: pools are CephString instead of CephPoolname because
   // these commands traditionally support both pool names and numbers
   r = admin_socket->register_command(
-   "setomapval",
    "setomapval " \
    "name=pool,type=CephString " \
    "name=objname,type=CephObjectname " \
@@ -2657,400 +3923,266 @@ void OSD::final_init()
    "name=val,type=CephString",
    test_ops_hook,
    "set omap key");
-  assert(r == 0);
+  ceph_assert(r == 0);
   r = admin_socket->register_command(
-    "rmomapkey",
     "rmomapkey " \
     "name=pool,type=CephString " \
     "name=objname,type=CephObjectname " \
     "name=key,type=CephString",
     test_ops_hook,
     "remove omap key");
-  assert(r == 0);
+  ceph_assert(r == 0);
   r = admin_socket->register_command(
-    "setomapheader",
     "setomapheader " \
     "name=pool,type=CephString " \
     "name=objname,type=CephObjectname " \
     "name=header,type=CephString",
     test_ops_hook,
     "set omap header");
-  assert(r == 0);
+  ceph_assert(r == 0);
 
   r = admin_socket->register_command(
-    "getomap",
     "getomap " \
     "name=pool,type=CephString " \
     "name=objname,type=CephObjectname",
     test_ops_hook,
     "output entire object map");
-  assert(r == 0);
+  ceph_assert(r == 0);
 
   r = admin_socket->register_command(
-    "truncobj",
     "truncobj " \
     "name=pool,type=CephString " \
     "name=objname,type=CephObjectname " \
     "name=len,type=CephInt",
     test_ops_hook,
     "truncate object to length");
-  assert(r == 0);
+  ceph_assert(r == 0);
 
   r = admin_socket->register_command(
-    "injectdataerr",
     "injectdataerr " \
     "name=pool,type=CephString " \
     "name=objname,type=CephObjectname " \
     "name=shardid,type=CephInt,req=false,range=0|255",
     test_ops_hook,
     "inject data error to an object");
-  assert(r == 0);
+  ceph_assert(r == 0);
 
   r = admin_socket->register_command(
-    "injectmdataerr",
     "injectmdataerr " \
     "name=pool,type=CephString " \
     "name=objname,type=CephObjectname " \
     "name=shardid,type=CephInt,req=false,range=0|255",
     test_ops_hook,
     "inject metadata error to an object");
-  assert(r == 0);
+  ceph_assert(r == 0);
   r = admin_socket->register_command(
-    "set_recovery_delay",
     "set_recovery_delay " \
     "name=utime,type=CephInt,req=false",
     test_ops_hook,
      "Delay osd recovery by specified seconds");
-  assert(r == 0);
-  r = admin_socket->register_command(
-   "trigger_scrub",
-   "trigger_scrub " \
-   "name=pgid,type=CephString ",
-   test_ops_hook,
-   "Trigger a scheduled scrub ");
-  assert(r == 0);
+  ceph_assert(r == 0);
   r = admin_socket->register_command(
-   "injectfull",
    "injectfull " \
    "name=type,type=CephString,req=false " \
    "name=count,type=CephInt,req=false ",
    test_ops_hook,
    "Inject a full disk (optional count times)");
-  assert(r == 0);
+  ceph_assert(r == 0);
+  r = admin_socket->register_command(
+    "bench " \
+    "name=count,type=CephInt,req=false "    \
+    "name=size,type=CephInt,req=false "                   \
+    "name=object_size,type=CephInt,req=false "    \
+    "name=object_num,type=CephInt,req=false ",
+    asok_hook,
+    "OSD benchmark: write <count> <size>-byte objects(with <obj_size> <obj_num>), " \
+    "(default count=1G default size=4MB). Results in log.");
+  ceph_assert(r == 0);
+  r = admin_socket->register_command(
+    "cluster_log " \
+    "name=level,type=CephChoices,strings=error,warning,info,debug "    \
+    "name=message,type=CephString,n=N",
+    asok_hook,
+    "log a message to the cluster log");
+  ceph_assert(r == 0);
+  r = admin_socket->register_command(
+    "flush_pg_stats",
+    asok_hook,
+    "flush pg stats");
+  ceph_assert(r == 0);
+  r = admin_socket->register_command(
+    "heap " \
+    "name=heapcmd,type=CephChoices,strings="                           \
+    "dump|start_profiler|stop_profiler|release|get_release_rate|set_release_rate|stats " \
+    "name=value,type=CephString,req=false",
+    asok_hook,
+    "show heap usage info (available only if compiled with tcmalloc)");
+  ceph_assert(r == 0);
+  r = admin_socket->register_command(
+    "debug dump_missing "                      \
+    "name=filename,type=CephFilepath",
+    asok_hook,
+    "dump missing objects to a named file");
+  ceph_assert(r == 0);
+  r = admin_socket->register_command(
+    "debug kick_recovery_wq "                                          \
+    "name=delay,type=CephInt,range=0",
+    asok_hook,
+    "set osd_recovery_delay_start to <val>");
+  ceph_assert(r == 0);
+  r = admin_socket->register_command(
+    "cpu_profiler "                                            \
+    "name=arg,type=CephChoices,strings=status|flush",
+    asok_hook,
+    "run cpu profiling on daemon");
+  ceph_assert(r == 0);
+  r = admin_socket->register_command(
+    "dump_pg_recovery_stats",
+    asok_hook,
+    "dump pg recovery statistics");
+  ceph_assert(r == 0);
+  r = admin_socket->register_command(
+    "reset_pg_recovery_stats",
+    asok_hook,
+    "reset pg recovery statistics");
+  ceph_assert(r == 0);
+  r = admin_socket->register_command(
+    "cache drop",
+    asok_hook,
+    "Drop all OSD caches");
+  ceph_assert(r == 0);
+  r = admin_socket->register_command(
+    "cache status",
+    asok_hook,
+    "Get OSD caches statistics");
+  ceph_assert(r == 0);
+  r = admin_socket->register_command(
+    "scrub_purged_snaps",
+    asok_hook,
+    "Scrub purged_snaps vs snapmapper index");
+  ceph_assert(r == 0);
+
+  // -- pg commands --
+  // old form: ceph pg <pgid> command ...
+  r = admin_socket->register_command(
+    "pg "                         \
+    "name=pgid,type=CephPgid "    \
+    "name=cmd,type=CephChoices,strings=query",
+    asok_hook,
+    "");
+  ceph_assert(r == 0);
+  r = admin_socket->register_command(
+    "pg "                         \
+    "name=pgid,type=CephPgid "    \
+    "name=cmd,type=CephChoices,strings=mark_unfound_lost " \
+    "name=mulcmd,type=CephChoices,strings=revert|delete",
+    asok_hook,
+    "");
+  ceph_assert(r == 0);
+  r = admin_socket->register_command(
+    "pg "                         \
+    "name=pgid,type=CephPgid "    \
+    "name=cmd,type=CephChoices,strings=list_unfound " \
+    "name=offset,type=CephString,req=false",
+    asok_hook,
+    "");
+  ceph_assert(r == 0);
+  r = admin_socket->register_command(
+    "pg "                         \
+    "name=pgid,type=CephPgid "    \
+    "name=cmd,type=CephChoices,strings=scrub " \
+    "name=time,type=CephInt,req=false",
+    asok_hook,
+    "");
+  ceph_assert(r == 0);
+  r = admin_socket->register_command(
+    "pg "                         \
+    "name=pgid,type=CephPgid "    \
+    "name=cmd,type=CephChoices,strings=deep_scrub " \
+    "name=time,type=CephInt,req=false",
+    asok_hook,
+    "");
+  ceph_assert(r == 0);
+  // new form: tell <pgid> <cmd> for both cli and rest
+  r = admin_socket->register_command(
+    "query",
+    asok_hook,
+    "show details of a specific pg");
+  ceph_assert(r == 0);
+  r = admin_socket->register_command(
+    "mark_unfound_lost "                                       \
+    "name=pgid,type=CephPgid,req=false "                       \
+    "name=mulcmd,type=CephChoices,strings=revert|delete",
+    asok_hook,
+    "mark all unfound objects in this pg as lost, either removing or reverting to a prior version if one is available");
+  ceph_assert(r == 0);
+  r = admin_socket->register_command(
+    "list_unfound "                                    \
+    "name=pgid,type=CephPgid,req=false "               \
+    "name=offset,type=CephString,req=false",
+    asok_hook,
+    "list unfound objects on this pg, perhaps starting at an offset given in JSON");
+  ceph_assert(r == 0);
+  r = admin_socket->register_command(
+    "scrub "                           \
+    "name=pgid,type=CephPgid,req=false "       \
+    "name=time,type=CephInt,req=false",
+    asok_hook,
+    "Trigger a scheduled scrub ");
+  ceph_assert(r == 0);
+  r = admin_socket->register_command(
+    "deep_scrub "                      \
+    "name=pgid,type=CephPgid,req=false "       \
+    "name=time,type=CephInt,req=false",
+    asok_hook,
+    "Trigger a scheduled deep scrub ");
+  ceph_assert(r == 0);
 }
 
-void OSD::create_logger()
+PerfCounters* OSD::create_logger()
 {
-  dout(10) << "create_logger" << dendl;
-
-  PerfCountersBuilder osd_plb(cct, "osd", l_osd_first, l_osd_last);
-
-  // Latency axis configuration for op histograms, values are in nanoseconds
-  PerfHistogramCommon::axis_config_d op_hist_x_axis_config{
-    "Latency (usec)",
-    PerfHistogramCommon::SCALE_LOG2, ///< Latency in logarithmic scale
-    0,                               ///< Start at 0
-    100000,                          ///< Quantization unit is 100usec
-    32,                              ///< Enough to cover much longer than slow requests
-  };
-
-  // Op size axis configuration for op histograms, values are in bytes
-  PerfHistogramCommon::axis_config_d op_hist_y_axis_config{
-    "Request size (bytes)",
-    PerfHistogramCommon::SCALE_LOG2, ///< Request size in logarithmic scale
-    0,                               ///< Start at 0
-    512,                             ///< Quantization unit is 512 bytes
-    32,                              ///< Enough to cover requests larger than GB
-  };
-
-
-  osd_plb.add_u64(
-    l_osd_op_wip, "op_wip",
-    "Replication operations currently being processed (primary)");
-  osd_plb.add_u64_counter(
-    l_osd_op, "op",
-    "Client operations",
-    "ops", PerfCountersBuilder::PRIO_CRITICAL);
-  osd_plb.add_u64_counter(
-    l_osd_op_inb,   "op_in_bytes",
-    "Client operations total write size",
-    "wr", PerfCountersBuilder::PRIO_INTERESTING);
-  osd_plb.add_u64_counter(
-    l_osd_op_outb,  "op_out_bytes",
-    "Client operations total read size",
-    "rd", PerfCountersBuilder::PRIO_INTERESTING);
-  osd_plb.add_time_avg(
-    l_osd_op_lat,   "op_latency",
-    "Latency of client operations (including queue time)",
-    "l", 9);
-  osd_plb.add_time_avg(
-    l_osd_op_process_lat, "op_process_latency",
-    "Latency of client operations (excluding queue time)");
-  osd_plb.add_time_avg(
-    l_osd_op_prepare_lat, "op_prepare_latency",
-    "Latency of client operations (excluding queue time and wait for finished)");
-
-  osd_plb.add_u64_counter(
-    l_osd_op_r, "op_r", "Client read operations");
-  osd_plb.add_u64_counter(
-    l_osd_op_r_outb, "op_r_out_bytes", "Client data read");
-  osd_plb.add_time_avg(
-    l_osd_op_r_lat, "op_r_latency",
-    "Latency of read operation (including queue time)");
-  osd_plb.add_histogram(
-    l_osd_op_r_lat_outb_hist, "op_r_latency_out_bytes_histogram",
-    op_hist_x_axis_config, op_hist_y_axis_config,
-    "Histogram of operation latency (including queue time) + data read");
-  osd_plb.add_time_avg(
-    l_osd_op_r_process_lat, "op_r_process_latency",
-    "Latency of read operation (excluding queue time)");
-  osd_plb.add_time_avg(
-    l_osd_op_r_prepare_lat, "op_r_prepare_latency",
-    "Latency of read operations (excluding queue time and wait for finished)");
-  osd_plb.add_u64_counter(
-    l_osd_op_w, "op_w", "Client write operations");
-  osd_plb.add_u64_counter(
-    l_osd_op_w_inb, "op_w_in_bytes", "Client data written");
-  osd_plb.add_time_avg(
-    l_osd_op_w_lat,  "op_w_latency",
-    "Latency of write operation (including queue time)");
-  osd_plb.add_histogram(
-    l_osd_op_w_lat_inb_hist, "op_w_latency_in_bytes_histogram",
-    op_hist_x_axis_config, op_hist_y_axis_config,
-    "Histogram of operation latency (including queue time) + data written");
-  osd_plb.add_time_avg(
-    l_osd_op_w_process_lat, "op_w_process_latency",
-    "Latency of write operation (excluding queue time)");
-  osd_plb.add_time_avg(
-    l_osd_op_w_prepare_lat, "op_w_prepare_latency",
-    "Latency of write operations (excluding queue time and wait for finished)");
-  osd_plb.add_u64_counter(
-    l_osd_op_rw, "op_rw",
-    "Client read-modify-write operations");
-  osd_plb.add_u64_counter(
-    l_osd_op_rw_inb, "op_rw_in_bytes",
-    "Client read-modify-write operations write in");
-  osd_plb.add_u64_counter(
-    l_osd_op_rw_outb,"op_rw_out_bytes",
-    "Client read-modify-write operations read out ");
-  osd_plb.add_time_avg(
-    l_osd_op_rw_lat, "op_rw_latency",
-    "Latency of read-modify-write operation (including queue time)");
-  osd_plb.add_histogram(
-    l_osd_op_rw_lat_inb_hist, "op_rw_latency_in_bytes_histogram",
-    op_hist_x_axis_config, op_hist_y_axis_config,
-    "Histogram of rw operation latency (including queue time) + data written");
-  osd_plb.add_histogram(
-    l_osd_op_rw_lat_outb_hist, "op_rw_latency_out_bytes_histogram",
-    op_hist_x_axis_config, op_hist_y_axis_config,
-    "Histogram of rw operation latency (including queue time) + data read");
-  osd_plb.add_time_avg(
-    l_osd_op_rw_process_lat, "op_rw_process_latency",
-    "Latency of read-modify-write operation (excluding queue time)");
-  osd_plb.add_time_avg(
-    l_osd_op_rw_prepare_lat, "op_rw_prepare_latency",
-    "Latency of read-modify-write operations (excluding queue time and wait for finished)");
-
-  osd_plb.add_u64_counter(
-    l_osd_sop, "subop", "Suboperations");
-  osd_plb.add_u64_counter(
-    l_osd_sop_inb, "subop_in_bytes", "Suboperations total size");
-  osd_plb.add_time_avg(l_osd_sop_lat, "subop_latency", "Suboperations latency");
-
-  osd_plb.add_u64_counter(l_osd_sop_w, "subop_w", "Replicated writes");
-  osd_plb.add_u64_counter(
-    l_osd_sop_w_inb, "subop_w_in_bytes", "Replicated written data size");
-  osd_plb.add_time_avg(
-    l_osd_sop_w_lat, "subop_w_latency", "Replicated writes latency");
-  osd_plb.add_u64_counter(
-    l_osd_sop_pull, "subop_pull", "Suboperations pull requests");
-  osd_plb.add_time_avg(
-    l_osd_sop_pull_lat, "subop_pull_latency", "Suboperations pull latency");
-  osd_plb.add_u64_counter(
-    l_osd_sop_push, "subop_push", "Suboperations push messages");
-  osd_plb.add_u64_counter(
-    l_osd_sop_push_inb, "subop_push_in_bytes", "Suboperations pushed size");
-  osd_plb.add_time_avg(
-    l_osd_sop_push_lat, "subop_push_latency", "Suboperations push latency");
-
-  osd_plb.add_u64_counter(l_osd_pull, "pull", "Pull requests sent");
-  osd_plb.add_u64_counter(l_osd_push, "push", "Push messages sent");
-  osd_plb.add_u64_counter(l_osd_push_outb, "push_out_bytes", "Pushed size");
-
-  osd_plb.add_u64_counter(
-    l_osd_rop, "recovery_ops",
-    "Started recovery operations",
-    "rop", PerfCountersBuilder::PRIO_INTERESTING);
-
-  osd_plb.add_u64(l_osd_loadavg, "loadavg", "CPU load");
-  osd_plb.add_u64(l_osd_buf, "buffer_bytes", "Total allocated buffer size");
-  osd_plb.add_u64(l_osd_history_alloc_bytes, "history_alloc_Mbytes");
-  osd_plb.add_u64(l_osd_history_alloc_num, "history_alloc_num");
-  osd_plb.add_u64(
-    l_osd_cached_crc, "cached_crc", "Total number getting crc from crc_cache");
-  osd_plb.add_u64(
-    l_osd_cached_crc_adjusted, "cached_crc_adjusted",
-    "Total number getting crc from crc_cache with adjusting");
-  osd_plb.add_u64(l_osd_missed_crc, "missed_crc", 
-    "Total number of crc cache misses");
-
-  osd_plb.add_u64(l_osd_pg, "numpg", "Placement groups",
-                 "pgs", PerfCountersBuilder::PRIO_USEFUL);
-  osd_plb.add_u64(
-    l_osd_pg_primary, "numpg_primary",
-    "Placement groups for which this osd is primary");
-  osd_plb.add_u64(
-    l_osd_pg_replica, "numpg_replica",
-    "Placement groups for which this osd is replica");
-  osd_plb.add_u64(
-    l_osd_pg_stray, "numpg_stray",
-    "Placement groups ready to be deleted from this osd");
-  osd_plb.add_u64(
-    l_osd_hb_to, "heartbeat_to_peers", "Heartbeat (ping) peers we send to");
-  osd_plb.add_u64_counter(l_osd_map, "map_messages", "OSD map messages");
-  osd_plb.add_u64_counter(l_osd_mape, "map_message_epochs", "OSD map epochs");
-  osd_plb.add_u64_counter(
-    l_osd_mape_dup, "map_message_epoch_dups", "OSD map duplicates");
-  osd_plb.add_u64_counter(
-    l_osd_waiting_for_map, "messages_delayed_for_map",
-    "Operations waiting for OSD map");
-  osd_plb.add_u64_counter(
-    l_osd_map_cache_hit, "osd_map_cache_hit", "osdmap cache hit");
-  osd_plb.add_u64_counter(
-    l_osd_map_cache_miss, "osd_map_cache_miss", "osdmap cache miss");
-  osd_plb.add_u64_counter(
-    l_osd_map_cache_miss_low, "osd_map_cache_miss_low",
-    "osdmap cache miss below cache lower bound");
-  osd_plb.add_u64_avg(
-    l_osd_map_cache_miss_low_avg, "osd_map_cache_miss_low_avg",
-    "osdmap cache miss, avg distance below cache lower bound");
-
-  osd_plb.add_u64(l_osd_stat_bytes, "stat_bytes", "OSD size");
-  osd_plb.add_u64(l_osd_stat_bytes_used, "stat_bytes_used", "Used space");
-  osd_plb.add_u64(l_osd_stat_bytes_avail, "stat_bytes_avail", "Available space");
-
-  osd_plb.add_u64_counter(
-    l_osd_copyfrom, "copyfrom", "Rados \"copy-from\" operations");
-
-  osd_plb.add_u64_counter(l_osd_tier_promote, "tier_promote", "Tier promotions");
-  osd_plb.add_u64_counter(l_osd_tier_flush, "tier_flush", "Tier flushes");
-  osd_plb.add_u64_counter(
-    l_osd_tier_flush_fail, "tier_flush_fail", "Failed tier flushes");
-  osd_plb.add_u64_counter(
-    l_osd_tier_try_flush, "tier_try_flush", "Tier flush attempts");
-  osd_plb.add_u64_counter(
-    l_osd_tier_try_flush_fail, "tier_try_flush_fail",
-    "Failed tier flush attempts");
-  osd_plb.add_u64_counter(
-    l_osd_tier_evict, "tier_evict", "Tier evictions");
-  osd_plb.add_u64_counter(
-    l_osd_tier_whiteout, "tier_whiteout", "Tier whiteouts");
-  osd_plb.add_u64_counter(
-    l_osd_tier_dirty, "tier_dirty", "Dirty tier flag set");
-  osd_plb.add_u64_counter(
-    l_osd_tier_clean, "tier_clean", "Dirty tier flag cleaned");
-  osd_plb.add_u64_counter(
-    l_osd_tier_delay, "tier_delay", "Tier delays (agent waiting)");
-  osd_plb.add_u64_counter(
-    l_osd_tier_proxy_read, "tier_proxy_read", "Tier proxy reads");
-  osd_plb.add_u64_counter(
-    l_osd_tier_proxy_write, "tier_proxy_write", "Tier proxy writes");
-
-  osd_plb.add_u64_counter(
-    l_osd_agent_wake, "agent_wake", "Tiering agent wake up");
-  osd_plb.add_u64_counter(
-    l_osd_agent_skip, "agent_skip", "Objects skipped by agent");
-  osd_plb.add_u64_counter(
-    l_osd_agent_flush, "agent_flush", "Tiering agent flushes");
-  osd_plb.add_u64_counter(
-    l_osd_agent_evict, "agent_evict", "Tiering agent evictions");
-
-  osd_plb.add_u64_counter(
-    l_osd_object_ctx_cache_hit, "object_ctx_cache_hit", "Object context cache hits");
-  osd_plb.add_u64_counter(
-    l_osd_object_ctx_cache_total, "object_ctx_cache_total", "Object context cache lookups");
-
-  osd_plb.add_u64_counter(l_osd_op_cache_hit, "op_cache_hit");
-  osd_plb.add_time_avg(
-    l_osd_tier_flush_lat, "osd_tier_flush_lat", "Object flush latency");
-  osd_plb.add_time_avg(
-    l_osd_tier_promote_lat, "osd_tier_promote_lat", "Object promote latency");
-  osd_plb.add_time_avg(
-    l_osd_tier_r_lat, "osd_tier_r_lat", "Object proxy read latency");
-
-  osd_plb.add_u64_counter(
-    l_osd_pg_info, "osd_pg_info", "PG updated its info (using any method)");
-  osd_plb.add_u64_counter(
-    l_osd_pg_fastinfo, "osd_pg_fastinfo",
-    "PG updated its info using fastinfo attr");
-  osd_plb.add_u64_counter(
-    l_osd_pg_biginfo, "osd_pg_biginfo", "PG updated its biginfo attr");
-
-  logger = osd_plb.create_perf_counters();
+  PerfCounters* logger = build_osd_logger(cct);
   cct->get_perfcounters_collection()->add(logger);
+  return logger;
 }
 
-void OSD::create_recoverystate_perf()
-{
-  dout(10) << "create_recoverystate_perf" << dendl;
-
-  PerfCountersBuilder rs_perf(cct, "recoverystate_perf", rs_first, rs_last);
-
-  rs_perf.add_time_avg(rs_initial_latency, "initial_latency", "Initial recovery state latency");
-  rs_perf.add_time_avg(rs_started_latency, "started_latency", "Started recovery state latency");
-  rs_perf.add_time_avg(rs_reset_latency, "reset_latency", "Reset recovery state latency");
-  rs_perf.add_time_avg(rs_start_latency, "start_latency", "Start recovery state latency");
-  rs_perf.add_time_avg(rs_primary_latency, "primary_latency", "Primary recovery state latency");
-  rs_perf.add_time_avg(rs_peering_latency, "peering_latency", "Peering recovery state latency");
-  rs_perf.add_time_avg(rs_backfilling_latency, "backfilling_latency", "Backfilling recovery state latency");
-  rs_perf.add_time_avg(rs_waitremotebackfillreserved_latency, "waitremotebackfillreserved_latency", "Wait remote backfill reserved recovery state latency");
-  rs_perf.add_time_avg(rs_waitlocalbackfillreserved_latency, "waitlocalbackfillreserved_latency", "Wait local backfill reserved recovery state latency");
-  rs_perf.add_time_avg(rs_notbackfilling_latency, "notbackfilling_latency", "Notbackfilling recovery state latency");
-  rs_perf.add_time_avg(rs_repnotrecovering_latency, "repnotrecovering_latency", "Repnotrecovering recovery state latency");
-  rs_perf.add_time_avg(rs_repwaitrecoveryreserved_latency, "repwaitrecoveryreserved_latency", "Rep wait recovery reserved recovery state latency");
-  rs_perf.add_time_avg(rs_repwaitbackfillreserved_latency, "repwaitbackfillreserved_latency", "Rep wait backfill reserved recovery state latency");
-  rs_perf.add_time_avg(rs_reprecovering_latency, "reprecovering_latency", "RepRecovering recovery state latency");
-  rs_perf.add_time_avg(rs_activating_latency, "activating_latency", "Activating recovery state latency");
-  rs_perf.add_time_avg(rs_waitlocalrecoveryreserved_latency, "waitlocalrecoveryreserved_latency", "Wait local recovery reserved recovery state latency");
-  rs_perf.add_time_avg(rs_waitremoterecoveryreserved_latency, "waitremoterecoveryreserved_latency", "Wait remote recovery reserved recovery state latency");
-  rs_perf.add_time_avg(rs_recovering_latency, "recovering_latency", "Recovering recovery state latency");
-  rs_perf.add_time_avg(rs_recovered_latency, "recovered_latency", "Recovered recovery state latency");
-  rs_perf.add_time_avg(rs_clean_latency, "clean_latency", "Clean recovery state latency");
-  rs_perf.add_time_avg(rs_active_latency, "active_latency", "Active recovery state latency");
-  rs_perf.add_time_avg(rs_replicaactive_latency, "replicaactive_latency", "Replicaactive recovery state latency");
-  rs_perf.add_time_avg(rs_stray_latency, "stray_latency", "Stray recovery state latency");
-  rs_perf.add_time_avg(rs_getinfo_latency, "getinfo_latency", "Getinfo recovery state latency");
-  rs_perf.add_time_avg(rs_getlog_latency, "getlog_latency", "Getlog recovery state latency");
-  rs_perf.add_time_avg(rs_waitactingchange_latency, "waitactingchange_latency", "Waitactingchange recovery state latency");
-  rs_perf.add_time_avg(rs_incomplete_latency, "incomplete_latency", "Incomplete recovery state latency");
-  rs_perf.add_time_avg(rs_down_latency, "down_latency", "Down recovery state latency");
-  rs_perf.add_time_avg(rs_getmissing_latency, "getmissing_latency", "Getmissing recovery state latency");
-  rs_perf.add_time_avg(rs_waitupthru_latency, "waitupthru_latency", "Waitupthru recovery state latency");
-  rs_perf.add_time_avg(rs_notrecovering_latency, "notrecovering_latency", "Notrecovering recovery state latency");
-
-  recoverystate_perf = rs_perf.create_perf_counters();
+PerfCounters* OSD::create_recoverystate_perf()
+{
+  PerfCounters* recoverystate_perf = build_recoverystate_perf(cct);
   cct->get_perfcounters_collection()->add(recoverystate_perf);
+  return recoverystate_perf;
 }
 
 int OSD::shutdown()
 {
+  if (cct->_conf->osd_fast_shutdown) {
+    derr << "*** Immediate shutdown (osd_fast_shutdown=true) ***" << dendl;
+    if (cct->_conf->osd_fast_shutdown_notify_mon)
+      service.prepare_to_stop();
+    cct->_log->flush();
+    _exit(0);
+  }
+
   if (!service.prepare_to_stop())
     return 0; // already shutting down
-  osd_lock.Lock();
+  osd_lock.lock();
   if (is_stopping()) {
-    osd_lock.Unlock();
+    osd_lock.unlock();
     return 0;
   }
-  derr << "shutdown" << dendl;
+  dout(0) << "shutdown" << dendl;
 
   set_state(STATE_STOPPING);
 
   // Debugging
-  cct->_conf->set_val("debug_osd", "100");
-  cct->_conf->set_val("debug_journal", "100");
-  cct->_conf->set_val("debug_filestore", "100");
-  cct->_conf->set_val("debug_ms", "100");
-  cct->_conf->apply_changes(NULL);
+  if (cct->_conf.get_val<bool>("osd_debug_shutdown")) {
+    cct->_conf.set_val("debug_osd", "100");
+    cct->_conf.set_val("debug_journal", "100");
+    cct->_conf.set_val("debug_filestore", "100");
+    cct->_conf.set_val("debug_bluestore", "100");
+    cct->_conf.set_val("debug_ms", "100");
+    cct->_conf.apply_changes(nullptr);
+  }
 
   // stop MgrClient earlier as it's more like an internal consumer of OSD
   mgrc.shutdown();
@@ -3063,146 +4195,119 @@ int OSD::shutdown()
 
   // Shutdown PGs
   {
-    RWLock::RLocker l(pg_map_lock);
-    for (ceph::unordered_map<spg_t, PG*>::iterator p = pg_map.begin();
-        p != pg_map.end();
-        ++p) {
-      dout(20) << " kicking pg " << p->first << dendl;
-      p->second->lock();
-      p->second->on_shutdown();
-      p->second->unlock();
-      p->second->osr->flush();
+    vector<PGRef> pgs;
+    _get_pgs(&pgs);
+    for (auto pg : pgs) {
+      pg->shutdown();
     }
   }
-  clear_pg_stat_queue();
 
   // drain op queue again (in case PGs requeued something)
   op_shardedwq.drain();
   {
     finished.clear(); // zap waiters (bleh, this is messy)
+    waiting_for_osdmap.clear();
   }
 
-  op_shardedwq.clear_pg_slots();
-
   // unregister commands
-  cct->get_admin_socket()->unregister_command("status");
-  cct->get_admin_socket()->unregister_command("flush_journal");
-  cct->get_admin_socket()->unregister_command("dump_ops_in_flight");
-  cct->get_admin_socket()->unregister_command("ops");
-  cct->get_admin_socket()->unregister_command("dump_blocked_ops");
-  cct->get_admin_socket()->unregister_command("dump_historic_ops");
-  cct->get_admin_socket()->unregister_command("dump_historic_ops_by_duration");
-  cct->get_admin_socket()->unregister_command("dump_historic_slow_ops");
-  cct->get_admin_socket()->unregister_command("dump_op_pq_state");
-  cct->get_admin_socket()->unregister_command("dump_blacklist");
-  cct->get_admin_socket()->unregister_command("dump_watchers");
-  cct->get_admin_socket()->unregister_command("dump_reservations");
-  cct->get_admin_socket()->unregister_command("get_latest_osdmap");
-  cct->get_admin_socket()->unregister_command("set_heap_property");
-  cct->get_admin_socket()->unregister_command("get_heap_property");
-  cct->get_admin_socket()->unregister_command("dump_objectstore_kv_stats");
-  cct->get_admin_socket()->unregister_command("calc_objectstore_db_histogram");
-  cct->get_admin_socket()->unregister_command("flush_store_cache");
-  cct->get_admin_socket()->unregister_command("dump_pgstate_history");
+  cct->get_admin_socket()->unregister_commands(asok_hook);
   delete asok_hook;
   asok_hook = NULL;
 
-  cct->get_admin_socket()->unregister_command("setomapval");
-  cct->get_admin_socket()->unregister_command("rmomapkey");
-  cct->get_admin_socket()->unregister_command("setomapheader");
-  cct->get_admin_socket()->unregister_command("getomap");
-  cct->get_admin_socket()->unregister_command("truncobj");
-  cct->get_admin_socket()->unregister_command("injectdataerr");
-  cct->get_admin_socket()->unregister_command("injectmdataerr");
-  cct->get_admin_socket()->unregister_command("set_recovery_delay");
+  cct->get_admin_socket()->unregister_commands(test_ops_hook);
   delete test_ops_hook;
   test_ops_hook = NULL;
 
-  osd_lock.Unlock();
+  osd_lock.unlock();
 
-  heartbeat_lock.Lock();
-  heartbeat_stop = true;
-  heartbeat_cond.Signal();
-  heartbeat_lock.Unlock();
+  {
+    std::lock_guard l{heartbeat_lock};
+    heartbeat_stop = true;
+    heartbeat_cond.notify_all();
+    heartbeat_peers.clear();
+  }
   heartbeat_thread.join();
 
-  osd_tp.drain();
-  peering_wq.clear();
-  osd_tp.stop();
-  dout(10) << "osd tp stopped" << dendl;
+  hb_back_server_messenger->mark_down_all();
+  hb_front_server_messenger->mark_down_all();
+  hb_front_client_messenger->mark_down_all();
+  hb_back_client_messenger->mark_down_all();
 
   osd_op_tp.drain();
   osd_op_tp.stop();
   dout(10) << "op sharded tp stopped" << dendl;
 
-  command_tp.drain();
-  command_tp.stop();
-  dout(10) << "command tp stopped" << dendl;
-
-  disk_tp.drain();
-  disk_tp.stop();
-  dout(10) << "disk tp paused (new)" << dendl;
-
   dout(10) << "stopping agent" << dendl;
   service.agent_stop();
 
-  osd_lock.Lock();
+  boot_finisher.wait_for_empty();
 
-  reset_heartbeat_peers();
+  osd_lock.lock();
+
+  boot_finisher.stop();
+  reset_heartbeat_peers(true);
 
   tick_timer.shutdown();
 
   {
-    Mutex::Locker l(tick_timer_lock);
+    std::lock_guard l(tick_timer_lock);
     tick_timer_without_osd_lock.shutdown();
   }
 
   // note unmount epoch
-  dout(10) << "noting clean unmount in epoch " << osdmap->get_epoch() << dendl;
+  dout(10) << "noting clean unmount in epoch " << get_osdmap_epoch() << dendl;
   superblock.mounted = service.get_boot_epoch();
-  superblock.clean_thru = osdmap->get_epoch();
+  superblock.clean_thru = get_osdmap_epoch();
   ObjectStore::Transaction t;
   write_superblock(t);
-  int r = store->apply_transaction(service.meta_osr.get(), std::move(t));
+  int r = store->queue_transaction(service.meta_ch, std::move(t));
   if (r) {
     derr << "OSD::shutdown: error writing superblock: "
         << cpp_strerror(r) << dendl;
   }
 
 
-  {
-    Mutex::Locker l(pg_stat_queue_lock);
-    assert(pg_stat_queue.empty());
-  }
+  service.shutdown_reserver();
 
   // Remove PGs
 #ifdef PG_DEBUG_REFS
   service.dump_live_pgids();
 #endif
-  {
-    RWLock::RLocker l(pg_map_lock);
-    for (ceph::unordered_map<spg_t, PG*>::iterator p = pg_map.begin();
-        p != pg_map.end();
-        ++p) {
-      dout(20) << " kicking pg " << p->first << dendl;
-      p->second->lock();
-      if (p->second->ref != 1) {
-        derr << "pgid " << p->first << " has ref count of "
-            << p->second->ref << dendl;
+  while (true) {
+    vector<PGRef> pgs;
+    _get_pgs(&pgs, true);
+    if (pgs.empty()) {
+      break;
+    }
+    for (auto& pg : pgs) {
+      if (pg->is_deleted()) {
+       continue;
+      }
+      dout(20) << " kicking pg " << pg << dendl;
+      pg->lock();
+      if (pg->get_num_ref() != 1) {
+       derr << "pgid " << pg->get_pgid() << " has ref count of "
+            << pg->get_num_ref() << dendl;
 #ifdef PG_DEBUG_REFS
-       p->second->dump_live_ids();
+       pg->dump_live_ids();
 #endif
-        ceph_abort();
+       if (cct->_conf->osd_shutdown_pgref_assert) {
+         ceph_abort();
+       }
       }
-      p->second->unlock();
-      p->second->put("PGMap");
+      pg->ch.reset();
+      pg->unlock();
     }
-    pg_map.clear();
   }
 #ifdef PG_DEBUG_REFS
   service.dump_live_pgids();
 #endif
-  cct->_conf->remove_observer(this);
+
+  osd_lock.unlock();
+  cct->_conf.remove_observer(this);
+  osd_lock.lock();
+
+  service.meta_ch.reset();
 
   dout(10) << "syncing store" << dendl;
   enable_disable_fuse(true);
@@ -3212,19 +4317,27 @@ int OSD::shutdown()
     store->flush_journal();
   }
 
+  monc->shutdown();
+  osd_lock.unlock();
+  {
+    std::unique_lock l{map_lock};
+    set_osdmap(OSDMapRef());
+  }
+  for (auto s : shards) {
+    std::lock_guard l(s->osdmap_lock);
+    s->shard_osdmap = OSDMapRef();
+  }
+  service.shutdown();
+
+  std::lock_guard lock(osd_lock);
   store->umount();
   delete store;
-  store = 0;
+  store = nullptr;
   dout(10) << "Store synced" << dendl;
 
-  monc->shutdown();
-  osd_lock.Unlock();
-
-  osdmap = OSDMapRef();
-  service.shutdown();
   op_tracker.on_shutdown();
 
-  class_handler->shutdown();
+  ClassHandler::get_instance().shutdown();
   client_messenger->shutdown();
   cluster_messenger->shutdown();
   hb_front_client_messenger->shutdown();
@@ -3233,8 +4346,6 @@ int OSD::shutdown()
   hb_front_server_messenger->shutdown();
   hb_back_server_messenger->shutdown();
 
-  peering_wq.clear();
-
   return r;
 }
 
@@ -3288,48 +4399,58 @@ int OSD::update_crush_location()
     snprintf(weight, sizeof(weight), "%.4lf", cct->_conf->osd_crush_initial_weight);
   } else {
     struct store_statfs_t st;
-    int r = store->statfs(&st);
+    osd_alert_list_t alerts;
+    int r = store->statfs(&st, &alerts);
     if (r < 0) {
       derr << "statfs: " << cpp_strerror(r) << dendl;
       return r;
     }
     snprintf(weight, sizeof(weight), "%.4lf",
-            MAX((double).00001,
-                (double)(st.total) /
-                (double)(1ull << 40 /* TB */)));
+            std::max(.00001,
+                     double(st.total) /
+                     double(1ull << 40 /* TB */)));
   }
 
-  std::multimap<string,string> loc = cct->crush_location.get_location();
-  dout(10) << __func__ << " crush location is " << loc << dendl;
+  dout(10) << __func__ << " crush location is " << cct->crush_location << dendl;
 
   string cmd =
     string("{\"prefix\": \"osd crush create-or-move\", ") +
-    string("\"id\": ") + stringify(whoami) + string(", ") +
-    string("\"weight\":") + weight + string(", ") +
-    string("\"args\": [");
-  for (multimap<string,string>::iterator p = loc.begin(); p != loc.end(); ++p) {
-    if (p != loc.begin())
-      cmd += ", ";
-    cmd += "\"" + p->first + "=" + p->second + "\"";
-  }
-  cmd += "]}";
-
+    string("\"id\": ") + stringify(whoami) + ", " +
+    string("\"weight\":") + weight + ", " +
+    string("\"args\": [") + stringify(cct->crush_location) + "]}";
   return mon_cmd_maybe_osd_create(cmd);
 }
 
 int OSD::update_crush_device_class()
 {
+  if (!cct->_conf->osd_class_update_on_start) {
+    dout(10) << __func__ << " osd_class_update_on_start = false" << dendl;
+    return 0;
+  }
+
   string device_class;
   int r = store->read_meta("crush_device_class", &device_class);
-  if (r < 0)
+  if (r < 0 || device_class.empty()) {
+    device_class = store->get_default_device_class();
+  }
+
+  if (device_class.empty()) {
+    dout(20) << __func__ << " no device class stored locally" << dendl;
     return 0;
+  }
 
   string cmd =
     string("{\"prefix\": \"osd crush set-device-class\", ") +
-    string("\"id\": ") + stringify(whoami) + string(", ") +
-    string("\"class\": \"") + device_class + string("\"}");
+    string("\"class\": \"") + device_class + string("\", ") +
+    string("\"ids\": [\"") + stringify(whoami) + string("\"]}");
 
-  return mon_cmd_maybe_osd_create(cmd);
+  r = mon_cmd_maybe_osd_create(cmd);
+  if (r == -EBUSY) {
+    // good, already bound to a device-class
+    return 0;
+  } else {
+    return r;
+  }
 }
 
 void OSD::write_superblock(ObjectStore::Transaction& t)
@@ -3341,19 +4462,19 @@ void OSD::write_superblock(ObjectStore::Transaction& t)
     superblock.compat_features.incompat.insert(CEPH_OSD_FEATURE_INCOMPAT_BASE);
 
   bufferlist bl;
-  ::encode(superblock, bl);
+  encode(superblock, bl);
   t.write(coll_t::meta(), OSD_SUPERBLOCK_GOBJECT, 0, bl.length(), bl);
 }
 
 int OSD::read_superblock()
 {
   bufferlist bl;
-  int r = store->read(coll_t::meta(), OSD_SUPERBLOCK_GOBJECT, 0, 0, bl);
+  int r = store->read(service.meta_ch, OSD_SUPERBLOCK_GOBJECT, 0, 0, bl);
   if (r < 0)
     return r;
 
-  bufferlist::iterator p = bl.begin();
-  ::decode(superblock, p);
+  auto p = bl.cbegin();
+  decode(superblock, p);
 
   dout(10) << "read_superblock " << superblock << dendl;
 
@@ -3377,7 +4498,9 @@ void OSD::clear_temp_objects()
     ghobject_t next;
     while (1) {
       vector<ghobject_t> objects;
-      store->collection_list(*p, next, ghobject_t::get_max(),
+      auto ch = store->open_collection(*p);
+      ceph_assert(ch);
+      store->collection_list(ch, next, ghobject_t::get_max(),
                             store->get_ideal_list_max(),
                             &objects, &next);
       if (objects.empty())
@@ -3403,13 +4526,13 @@ void OSD::clear_temp_objects()
        dout(20) << "  removing " << *p << " object " << *q << dendl;
        t.remove(*p, *q);
         if (++removed > cct->_conf->osd_target_transaction_size) {
-          store->apply_transaction(service.meta_osr.get(), std::move(t));
+          store->queue_transaction(service.meta_ch, std::move(t));
           t = ObjectStore::Transaction();
           removed = 0;
         }
       }
       if (removed) {
-        store->apply_transaction(service.meta_osr.get(), std::move(t));
+        store->queue_transaction(service.meta_ch, std::move(t));
       }
     }
   }
@@ -3424,38 +4547,38 @@ void OSD::recursive_remove_collection(CephContext* cct,
     coll_t(),
     make_snapmapper_oid());
 
-  ceph::shared_ptr<ObjectStore::Sequencer> osr (std::make_shared<
-                                      ObjectStore::Sequencer>("rm"));
+  ObjectStore::CollectionHandle ch = store->open_collection(tmp);
   ObjectStore::Transaction t;
   SnapMapper mapper(cct, &driver, 0, 0, 0, pgid.shard);
 
+  ghobject_t next;
+  int max = cct->_conf->osd_target_transaction_size;
   vector<ghobject_t> objects;
-  store->collection_list(tmp, ghobject_t(), ghobject_t::get_max(),
-                        INT_MAX, &objects, 0);
-  generic_dout(10) << __func__ << " " << objects << dendl;
-  // delete them.
-  int removed = 0;
-  for (vector<ghobject_t>::iterator p = objects.begin();
-       p != objects.end();
-       ++p, removed++) {
-    OSDriver::OSTransaction _t(driver.get_transaction(&t));
-    int r = mapper.remove_oid(p->hobj, &_t);
-    if (r != 0 && r != -ENOENT)
-      ceph_abort();
-    t.remove(tmp, *p);
-    if (removed > cct->_conf->osd_target_transaction_size) {
-      int r = store->apply_transaction(osr.get(), std::move(t));
-      assert(r == 0);
-      t = ObjectStore::Transaction();
-      removed = 0;
+  objects.reserve(max);
+  while (true) {
+    objects.clear();
+    store->collection_list(ch, next, ghobject_t::get_max(),
+      max, &objects, &next);
+    generic_dout(10) << __func__ << " " << objects << dendl;
+    if (objects.empty())
+      break;
+    for (auto& p: objects) {
+      OSDriver::OSTransaction _t(driver.get_transaction(&t));
+      int r = mapper.remove_oid(p.hobj, &_t);
+      if (r != 0 && r != -ENOENT)
+        ceph_abort();
+      t.remove(tmp, p);
     }
+    int r = store->queue_transaction(ch, std::move(t));
+    ceph_assert(r == 0);
+    t = ObjectStore::Transaction();
   }
   t.remove_collection(tmp);
-  int r = store->apply_transaction(osr.get(), std::move(t));
-  assert(r == 0);
+  int r = store->queue_transaction(ch, std::move(t));
+  ceph_assert(r == 0);
 
   C_SaferCond waiter;
-  if (!osr->flush_commit(&waiter)) {
+  if (!ch->flush_commit(&waiter)) {
     waiter.wait();
   }
 }
@@ -3464,201 +4587,177 @@ void OSD::recursive_remove_collection(CephContext* cct,
 // ======================================================
 // PG's
 
-PGPool OSD::_get_pool(int id, OSDMapRef createmap)
-{
-  if (!createmap->have_pg_pool(id)) {
-    dout(5) << __func__ << ": the OSDmap does not contain a PG pool with id = "
-           << id << dendl;
-    ceph_abort();
-  }
-
-  PGPool p = PGPool(cct, createmap, id);
-
-  dout(10) << "_get_pool " << p.id << dendl;
-  return p;
-}
-
-PG *OSD::_open_lock_pg(
-  OSDMapRef createmap,
-  spg_t pgid, bool no_lockdep_check)
-{
-  assert(osd_lock.is_locked());
-
-  PG* pg = _make_pg(createmap, pgid);
-  {
-    RWLock::WLocker l(pg_map_lock);
-    pg->lock(no_lockdep_check);
-    pg_map[pgid] = pg;
-    pg->get("PGMap");  // because it's in pg_map
-    service.pg_add_epoch(pg->info.pgid, createmap->get_epoch());
-  }
-  return pg;
-}
-
 PG* OSD::_make_pg(
   OSDMapRef createmap,
   spg_t pgid)
 {
-  dout(10) << "_open_lock_pg " << pgid << dendl;
-  PGPool pool = _get_pool(pgid.pool(), createmap);
-
-  // create
+  dout(10) << __func__ << " " << pgid << dendl;
+  pg_pool_t pi;
+  map<string,string> ec_profile;
+  string name;
+  if (createmap->have_pg_pool(pgid.pool())) {
+    pi = *createmap->get_pg_pool(pgid.pool());
+    name = createmap->get_pool_name(pgid.pool());
+    if (pi.is_erasure()) {
+      ec_profile = createmap->get_erasure_code_profile(pi.erasure_code_profile);
+    }
+  } else {
+    // pool was deleted; grab final pg_pool_t off disk.
+    ghobject_t oid = make_final_pool_info_oid(pgid.pool());
+    bufferlist bl;
+    int r = store->read(service.meta_ch, oid, 0, 0, bl);
+    if (r < 0) {
+      derr << __func__ << " missing pool " << pgid.pool() << " tombstone"
+          << dendl;
+      return nullptr;
+    }
+    ceph_assert(r >= 0);
+    auto p = bl.cbegin();
+    decode(pi, p);
+    decode(name, p);
+    if (p.end()) { // dev release v13.0.2 did not include ec_profile
+      derr << __func__ << " missing ec_profile from pool " << pgid.pool()
+          << " tombstone" << dendl;
+      return nullptr;
+    }
+    decode(ec_profile, p);
+  }
+  PGPool pool(createmap, pgid.pool(), pi, name);
   PG *pg;
-  if (createmap->get_pg_type(pgid.pgid) == pg_pool_t::TYPE_REPLICATED ||
-      createmap->get_pg_type(pgid.pgid) == pg_pool_t::TYPE_ERASURE)
-    pg = new PrimaryLogPG(&service, createmap, pool, pgid);
+  if (pi.type == pg_pool_t::TYPE_REPLICATED ||
+      pi.type == pg_pool_t::TYPE_ERASURE)
+    pg = new PrimaryLogPG(&service, createmap, pool, ec_profile, pgid);
   else
     ceph_abort();
-
   return pg;
 }
 
-
-void OSD::add_newly_split_pg(PG *pg, PG::RecoveryCtx *rctx)
+void OSD::_get_pgs(vector<PGRef> *v, bool clear_too)
 {
-  epoch_t e(service.get_osdmap()->get_epoch());
-  pg->get("PGMap");  // For pg_map
-  pg_map[pg->info.pgid] = pg;
-  service.pg_add_epoch(pg->info.pgid, pg->get_osdmap()->get_epoch());
-
-  dout(10) << "Adding newly split pg " << *pg << dendl;
-  pg->handle_loaded(rctx);
-  pg->write_if_dirty(*(rctx->transaction));
-  pg->queue_null(e, e);
-  map<spg_t, list<PG::CephPeeringEvtRef> >::iterator to_wake =
-    peering_wait_for_split.find(pg->info.pgid);
-  if (to_wake != peering_wait_for_split.end()) {
-    for (list<PG::CephPeeringEvtRef>::iterator i =
-          to_wake->second.begin();
-        i != to_wake->second.end();
-        ++i) {
-      pg->queue_peering_event(*i);
+  v->clear();
+  v->reserve(get_num_pgs());
+  for (auto& s : shards) {
+    std::lock_guard l(s->shard_lock);
+    for (auto& j : s->pg_slots) {
+      if (j.second->pg &&
+         !j.second->pg->is_deleted()) {
+       v->push_back(j.second->pg);
+       if (clear_too) {
+         s->_detach_pg(j.second.get());
+       }
+      }
     }
-    peering_wait_for_split.erase(to_wake);
   }
-  if (!service.get_osdmap()->have_pg_pool(pg->info.pgid.pool()))
-    _remove_pg(pg);
 }
 
-OSD::res_result OSD::_try_resurrect_pg(
-  OSDMapRef curmap, spg_t pgid, spg_t *resurrected, PGRef *old_pg_state)
+void OSD::_get_pgids(vector<spg_t> *v)
 {
-  assert(resurrected);
-  assert(old_pg_state);
-  // find nearest ancestor
-  DeletingStateRef df;
-  spg_t cur(pgid);
-  while (true) {
-    df = service.deleting_pgs.lookup(cur);
-    if (df)
-      break;
-    if (!cur.ps())
-      break;
-    cur = cur.get_parent();
-  }
-  if (!df)
-    return RES_NONE; // good to go
-
-  df->old_pg_state->lock();
-  OSDMapRef create_map = df->old_pg_state->get_osdmap();
-  df->old_pg_state->unlock();
-
-  set<spg_t> children;
-  if (cur == pgid) {
-    if (df->try_stop_deletion()) {
-      dout(10) << __func__ << ": halted deletion on pg " << pgid << dendl;
-      *resurrected = cur;
-      *old_pg_state = df->old_pg_state;
-      service.deleting_pgs.remove(pgid); // PG is no longer being removed!
-      return RES_SELF;
-    } else {
-      // raced, ensure we don't see DeletingStateRef when we try to
-      // delete this pg
-      service.deleting_pgs.remove(pgid);
-      return RES_NONE;
-    }
-  } else if (cur.is_split(create_map->get_pg_num(cur.pool()),
-                         curmap->get_pg_num(cur.pool()),
-                         &children) &&
-            children.count(pgid)) {
-    if (df->try_stop_deletion()) {
-      dout(10) << __func__ << ": halted deletion on ancestor pg " << pgid
-              << dendl;
-      *resurrected = cur;
-      *old_pg_state = df->old_pg_state;
-      service.deleting_pgs.remove(cur); // PG is no longer being removed!
-      return RES_PARENT;
-    } else {
-      /* this is not a problem, failing to cancel proves that all objects
-       * have been removed, so no hobject_t overlap is possible
-       */
-      return RES_NONE;
+  v->clear();
+  v->reserve(get_num_pgs());
+  for (auto& s : shards) {
+    std::lock_guard l(s->shard_lock);
+    for (auto& j : s->pg_slots) {
+      if (j.second->pg &&
+         !j.second->pg->is_deleted()) {
+       v->push_back(j.first);
+      }
     }
   }
-  return RES_NONE;
 }
 
-PG *OSD::_create_lock_pg(
-  OSDMapRef createmap,
-  spg_t pgid,
-  bool hold_map_lock,
-  bool backfill,
-  int role,
-  vector<int>& up, int up_primary,
-  vector<int>& acting, int acting_primary,
-  pg_history_t history,
-  const PastIntervals& pi,
-  ObjectStore::Transaction& t)
+void OSD::register_pg(PGRef pg)
 {
-  assert(osd_lock.is_locked());
-  dout(20) << "_create_lock_pg pgid " << pgid << dendl;
+  spg_t pgid = pg->get_pgid();
+  uint32_t shard_index = pgid.hash_to_shard(num_shards);
+  auto sdata = shards[shard_index];
+  std::lock_guard l(sdata->shard_lock);
+  auto r = sdata->pg_slots.emplace(pgid, make_unique<OSDShardPGSlot>());
+  ceph_assert(r.second);
+  auto *slot = r.first->second.get();
+  dout(20) << __func__ << " " << pgid << " " << pg << dendl;
+  sdata->_attach_pg(slot, pg.get());
+}
 
-  PG *pg = _open_lock_pg(createmap, pgid, true);
+bool OSD::try_finish_pg_delete(PG *pg, unsigned old_pg_num)
+{
+  auto sdata = pg->osd_shard;
+  ceph_assert(sdata);
+  {
+    std::lock_guard l(sdata->shard_lock);
+    auto p = sdata->pg_slots.find(pg->pg_id);
+    if (p == sdata->pg_slots.end() ||
+       !p->second->pg) {
+      dout(20) << __func__ << " " << pg->pg_id << " not found" << dendl;
+      return false;
+    }
+    if (p->second->waiting_for_merge_epoch) {
+      dout(20) << __func__ << " " << pg->pg_id << " waiting for merge" << dendl;
+      return false;
+    }
+    dout(20) << __func__ << " " << pg->pg_id << " " << pg << dendl;
+    sdata->_detach_pg(p->second.get());
+  }
 
-  service.init_splits_between(pgid, pg->get_osdmap(), service.get_osdmap());
+  for (auto shard : shards) {
+    shard->unprime_split_children(pg->pg_id, old_pg_num);
+  }
 
-  pg->init(
-    role,
-    up,
-    up_primary,
-    acting,
-    acting_primary,
-    history,
-    pi,
-    backfill,
-    &t);
+  // update pg count now since we might not get an osdmap any time soon.
+  if (pg->is_primary())
+    service.logger->dec(l_osd_pg_primary);
+  else if (pg->is_nonprimary())
+    service.logger->dec(l_osd_pg_replica); // misnomver
+  else
+    service.logger->dec(l_osd_pg_stray);
 
-  dout(7) << "_create_lock_pg " << *pg << dendl;
-  return pg;
+  return true;
 }
 
-PG *OSD::_lookup_lock_pg(spg_t pgid)
+PGRef OSD::_lookup_pg(spg_t pgid)
 {
-  RWLock::RLocker l(pg_map_lock);
-
-  auto pg_map_entry = pg_map.find(pgid);
-  if (pg_map_entry == pg_map.end())
+  uint32_t shard_index = pgid.hash_to_shard(num_shards);
+  auto sdata = shards[shard_index];
+  std::lock_guard l(sdata->shard_lock);
+  auto p = sdata->pg_slots.find(pgid);
+  if (p == sdata->pg_slots.end()) {
     return nullptr;
-  PG *pg = pg_map_entry->second;
-  pg->lock();
-  return pg;
+  }
+  return p->second->pg;
 }
 
-PG *OSD::_lookup_lock_pg_with_map_lock_held(spg_t pgid)
+PGRef OSD::_lookup_lock_pg(spg_t pgid)
 {
-  assert(pg_map.count(pgid));
-  PG *pg = pg_map[pgid];
-  pg->lock();
-  return pg;
+  PGRef pg = _lookup_pg(pgid);
+  if (!pg) {
+    return nullptr;
+  }
+  pg->lock();
+  if (!pg->is_deleted()) {
+    return pg;
+  }
+  pg->unlock();
+  return nullptr;
+}
+
+PGRef OSD::lookup_lock_pg(spg_t pgid)
+{
+  return _lookup_lock_pg(pgid);
 }
 
 void OSD::load_pgs()
 {
-  assert(osd_lock.is_locked());
+  ceph_assert(ceph_mutex_is_locked(osd_lock));
   dout(0) << "load_pgs" << dendl;
+
   {
-    RWLock::RLocker l(pg_map_lock);
-    assert(pg_map.empty());
+    auto pghist = make_pg_num_history_oid();
+    bufferlist bl;
+    int r = store->read(service.meta_ch, pghist, 0, 0, bl, 0);
+    if (r >= 0 && bl.length() > 0) {
+      auto p = bl.cbegin();
+      decode(pg_num_history, p);
+    }
+    dout(20) << __func__ << " pg_num_history " << pg_num_history << dendl;
   }
 
   vector<coll_t> ls;
@@ -3667,15 +4766,15 @@ void OSD::load_pgs()
     derr << "failed to list pgs: " << cpp_strerror(-r) << dendl;
   }
 
-  bool has_upgraded = false;
-
+  int num = 0;
   for (vector<coll_t>::iterator it = ls.begin();
        it != ls.end();
        ++it) {
     spg_t pgid;
     if (it->is_temp(&pgid) ||
        (it->is_pg(&pgid) && PG::_has_removal_flag(store, pgid))) {
-      dout(10) << "load_pgs " << *it << " clearing temp" << dendl;
+      dout(10) << "load_pgs " << *it
+              << " removing, legacy or flagged for removal pg" << dendl;
       recursive_remove_collection(cct, store, pgid, *it);
       continue;
     }
@@ -3685,27 +4784,20 @@ void OSD::load_pgs()
       continue;
     }
 
-    if (pgid.preferred() >= 0) {
-      dout(10) << __func__ << ": skipping localized PG " << pgid << dendl;
-      // FIXME: delete it too, eventually
-      continue;
-    }
-
     dout(10) << "pgid " << pgid << " coll " << coll_t(pgid) << dendl;
-    bufferlist bl;
     epoch_t map_epoch = 0;
-    int r = PG::peek_map_epoch(store, pgid, &map_epoch, &bl);
+    int r = PG::peek_map_epoch(store, pgid, &map_epoch);
     if (r < 0) {
       derr << __func__ << " unable to peek at " << pgid << " metadata, skipping"
           << dendl;
       continue;
     }
 
-    PG *pg = NULL;
+    PGRef pg;
     if (map_epoch > 0) {
       OSDMapRef pgosdmap = service.try_get_map(map_epoch);
       if (!pgosdmap) {
-       if (!osdmap->have_pg_pool(pgid.pool())) {
+       if (!get_osdmap()->have_pg_pool(pgid.pool())) {
          derr << __func__ << ": could not find map for epoch " << map_epoch
               << " on pg " << pgid << ", but the pool is not present in the "
               << "current map, so this is probably a result of bug 10617.  "
@@ -3716,444 +4808,251 @@ void OSD::load_pgs()
          derr << __func__ << ": have pgid " << pgid << " at epoch "
               << map_epoch << ", but missing map.  Crashing."
               << dendl;
-         assert(0 == "Missing map in load_pgs");
+         ceph_abort_msg("Missing map in load_pgs");
        }
       }
-      pg = _open_lock_pg(pgosdmap, pgid);
+      pg = _make_pg(pgosdmap, pgid);
     } else {
-      pg = _open_lock_pg(osdmap, pgid);
+      pg = _make_pg(get_osdmap(), pgid);
+    }
+    if (!pg) {
+      recursive_remove_collection(cct, store, pgid, *it);
+      continue;
     }
-    // there can be no waiters here, so we don't call wake_pg_waiters
 
+    // there can be no waiters here, so we don't call _wake_pg_slot
+
+    pg->lock();
     pg->ch = store->open_collection(pg->coll);
 
     // read pg state, log
-    pg->read_state(store, bl);
+    pg->read_state(store);
 
-    if (pg->must_upgrade()) {
-      if (!pg->can_upgrade()) {
-       derr << "PG needs upgrade, but on-disk data is too old; upgrade to"
-            << " an older version first." << dendl;
-       assert(0 == "PG too old to upgrade");
-      }
-      if (!has_upgraded) {
-       derr << "PGs are upgrading" << dendl;
-       has_upgraded = true;
-      }
-      dout(10) << "PG " << pg->info.pgid
-              << " must upgrade..." << dendl;
-      pg->upgrade(store);
+    if (pg->dne())  {
+      dout(10) << "load_pgs " << *it << " deleting dne" << dendl;
+      pg->ch = nullptr;
+      pg->unlock();
+      recursive_remove_collection(cct, store, pgid, *it);
+      continue;
+    }
+    {
+      uint32_t shard_index = pgid.hash_to_shard(shards.size());
+      assert(NULL != shards[shard_index]);
+      store->set_collection_commit_queue(pg->coll, &(shards[shard_index]->context_queue));
     }
-
-    service.init_splits_between(pg->info.pgid, pg->get_osdmap(), osdmap);
-
-    // generate state for PG's current mapping
-    int primary, up_primary;
-    vector<int> acting, up;
-    pg->get_osdmap()->pg_to_up_acting_osds(
-      pgid.pgid, &up, &up_primary, &acting, &primary);
-    pg->init_primary_up_acting(
-      up,
-      acting,
-      up_primary,
-      primary);
-    int role = OSDMap::calc_pg_role(whoami, pg->acting);
-    if (pg->pool.info.is_replicated() || role == pg->pg_whoami.shard)
-      pg->set_role(role);
-    else
-      pg->set_role(-1);
 
     pg->reg_next_scrub();
 
-    PG::RecoveryCtx rctx(0, 0, 0, 0, 0, 0);
-    pg->handle_loaded(&rctx);
-
-    dout(10) << "load_pgs loaded " << *pg << " " << pg->pg_log.get_log() << dendl;
-    if (pg->pg_log.is_dirty()) {
-      ObjectStore::Transaction t;
-      pg->write_if_dirty(t);
-      store->apply_transaction(pg->osr.get(), std::move(t));
-    }
+    dout(10) << __func__ << " loaded " << *pg << dendl;
     pg->unlock();
-  }
-  {
-    RWLock::RLocker l(pg_map_lock);
-    dout(0) << "load_pgs opened " << pg_map.size() << " pgs" << dendl;
-  }
 
-  // clean up old infos object?
-  if (has_upgraded && store->exists(coll_t::meta(), OSD::make_infos_oid())) {
-    dout(1) << __func__ << " removing legacy infos object" << dendl;
-    ObjectStore::Transaction t;
-    t.remove(coll_t::meta(), OSD::make_infos_oid());
-    int r = store->apply_transaction(service.meta_osr.get(), std::move(t));
-    if (r != 0) {
-      derr << __func__ << ": apply_transaction returned "
-          << cpp_strerror(r) << dendl;
-      ceph_abort();
-    }
+    register_pg(pg);
+    ++num;
   }
-
-  build_past_intervals_parallel();
+  dout(0) << __func__ << " opened " << num << " pgs" << dendl;
 }
 
 
-/*
- * build past_intervals efficiently on old, degraded, and buried
- * clusters.  this is important for efficiently catching up osds that
- * are way behind on maps to the current cluster state.
- *
- * this is a parallel version of PG::generate_past_intervals().
- * follow the same logic, but do all pgs at the same time so that we
- * can make a single pass across the osdmap history.
- */
-void OSD::build_past_intervals_parallel()
-{
-  struct pistate {
-    epoch_t start, end;
-    vector<int> old_acting, old_up;
-    epoch_t same_interval_since;
-    int primary;
-    int up_primary;
-  };
-  map<PG*,pistate> pis;
+PGRef OSD::handle_pg_create_info(const OSDMapRef& osdmap,
+                                const PGCreateInfo *info)
+{
+  spg_t pgid = info->pgid;
 
-  // calculate junction of map range
-  epoch_t end_epoch = superblock.oldest_map;
-  epoch_t cur_epoch = superblock.newest_map;
-  {
-    RWLock::RLocker l(pg_map_lock);
-    for (ceph::unordered_map<spg_t, PG*>::iterator i = pg_map.begin();
-        i != pg_map.end();
-        ++i) {
-      PG *pg = i->second;
-
-      auto rpib = pg->get_required_past_interval_bounds(
-       pg->info,
-       superblock.oldest_map);
-      if (rpib.first >= rpib.second && pg->past_intervals.empty()) {
-        if (pg->info.history.same_interval_since == 0) {
-          pg->info.history.same_interval_since = rpib.second;
-       }
-       continue;
-      } else {
-       auto apib = pg->past_intervals.get_bounds();
-       if (apib.second >= rpib.second &&
-           apib.first <= rpib.first) {
-         if (pg->info.history.same_interval_since == 0) {
-           pg->info.history.same_interval_since = rpib.second;
-         }
-         continue;
-       }
-      }
+  if (maybe_wait_for_max_pg(osdmap, pgid, info->by_mon)) {
+    dout(10) << __func__ << " hit max pg, dropping" << dendl;
+    return nullptr;
+  }
+
+  PeeringCtx rctx = create_context();
 
-      dout(10) << pg->info.pgid << " needs " << rpib.first << "-"
-              << rpib.second << dendl;
-      pistate& p = pis[pg];
-      p.start = rpib.first;
-      p.end = rpib.second;
-      p.same_interval_since = 0;
+  OSDMapRef startmap = get_map(info->epoch);
 
-      if (rpib.first < cur_epoch)
-        cur_epoch = rpib.first;
-      if (rpib.second > end_epoch)
-        end_epoch = rpib.second;
+  if (info->by_mon) {
+    int64_t pool_id = pgid.pgid.pool();
+    const pg_pool_t *pool = osdmap->get_pg_pool(pool_id);
+    if (!pool) {
+      dout(10) << __func__ << " ignoring " << pgid << ", pool dne" << dendl;
+      return nullptr;
+    }
+    if (osdmap->require_osd_release >= ceph_release_t::nautilus &&
+       !pool->has_flag(pg_pool_t::FLAG_CREATING)) {
+      // this ensures we do not process old creating messages after the
+      // pool's initial pgs have been created (and pg are subsequently
+      // allowed to split or merge).
+      dout(20) << __func__ << "  dropping " << pgid
+              << "create, pool does not have CREATING flag set" << dendl;
+      return nullptr;
     }
-  }
-  if (pis.empty()) {
-    dout(10) << __func__ << " nothing to build" << dendl;
-    return;
   }
 
-  dout(1) << __func__ << " over " << cur_epoch << "-" << end_epoch << dendl;
-  assert(cur_epoch <= end_epoch);
+  int up_primary, acting_primary;
+  vector<int> up, acting;
+  startmap->pg_to_up_acting_osds(
+    pgid.pgid, &up, &up_primary, &acting, &acting_primary);
 
-  OSDMapRef cur_map, last_map;
-  for ( ; cur_epoch <= end_epoch; cur_epoch++) {
-    dout(10) << __func__ << " epoch " << cur_epoch << dendl;
-    last_map = cur_map;
-    cur_map = get_map(cur_epoch);
+  const pg_pool_t* pp = startmap->get_pg_pool(pgid.pool());
+  if (pp->has_flag(pg_pool_t::FLAG_EC_OVERWRITES) &&
+      store->get_type() != "bluestore") {
+    clog->warn() << "pg " << pgid
+                << " is at risk of silent data corruption: "
+                << "the pool allows ec overwrites but is not stored in "
+                << "bluestore, so deep scrubbing will not detect bitrot";
+  }
+  create_pg_collection(
+    rctx.transaction, pgid, pgid.get_split_bits(pp->get_pg_num()));
+  init_pg_ondisk(rctx.transaction, pgid, pp);
 
-    for (map<PG*,pistate>::iterator i = pis.begin(); i != pis.end(); ++i) {
-      PG *pg = i->first;
-      pistate& p = i->second;
+  int role = startmap->calc_pg_role(pg_shard_t(whoami, pgid.shard), acting);
 
-      if (cur_epoch < p.start || cur_epoch > p.end)
-       continue;
+  PGRef pg = _make_pg(startmap, pgid);
+  pg->ch = store->create_new_collection(pg->coll);
 
-      vector<int> acting, up;
-      int up_primary;
-      int primary;
-      pg_t pgid = pg->info.pgid.pgid;
-      if (p.same_interval_since && last_map->get_pools().count(pgid.pool()))
-       pgid = pgid.get_ancestor(last_map->get_pg_num(pgid.pool()));
-      cur_map->pg_to_up_acting_osds(
-       pgid, &up, &up_primary, &acting, &primary);
-
-      if (p.same_interval_since == 0) {
-       dout(10) << __func__ << " epoch " << cur_epoch << " pg " << pg->info.pgid
-                << " first map, acting " << acting
-                << " up " << up << ", same_interval_since = " << cur_epoch << dendl;
-       p.same_interval_since = cur_epoch;
-       p.old_up = up;
-       p.old_acting = acting;
-       p.primary = primary;
-       p.up_primary = up_primary;
-       continue;
-      }
-      assert(last_map);
-
-      boost::scoped_ptr<IsPGRecoverablePredicate> recoverable(
-        pg->get_is_recoverable_predicate());
-      std::stringstream debug;
-      bool new_interval = PastIntervals::check_new_interval(
-       p.primary,
-       primary,
-       p.old_acting, acting,
-       p.up_primary,
-       up_primary,
-       p.old_up, up,
-       p.same_interval_since,
-       pg->info.history.last_epoch_clean,
-       cur_map, last_map,
-       pgid,
-        recoverable.get(),
-       &pg->past_intervals,
-       &debug);
-      if (new_interval) {
-       dout(10) << __func__ << " epoch " << cur_epoch << " pg " << pg->info.pgid
-                << " " << debug.str() << dendl;
-       p.old_up = up;
-       p.old_acting = acting;
-       p.primary = primary;
-       p.up_primary = up_primary;
-       p.same_interval_since = cur_epoch;
-      }
-    }
+  {
+    uint32_t shard_index = pgid.hash_to_shard(shards.size());
+    assert(NULL != shards[shard_index]);
+    store->set_collection_commit_queue(pg->coll, &(shards[shard_index]->context_queue));
   }
 
-  // Now that past_intervals have been recomputed let's fix the same_interval_since
-  // if it was cleared by import.
-  for (map<PG*,pistate>::iterator i = pis.begin(); i != pis.end(); ++i) {
-    PG *pg = i->first;
-    pistate& p = i->second;
+  pg->lock(true);
 
-    if (pg->info.history.same_interval_since == 0) {
-      assert(p.same_interval_since);
-      dout(10) << __func__ << " fix same_interval_since " << p.same_interval_since << " pg " << *pg << dendl;
-      dout(10) << __func__ << " past_intervals " << pg->past_intervals << dendl;
-      // Fix it
-      pg->info.history.same_interval_since = p.same_interval_since;
-    }
-  }
+  // we are holding the shard lock
+  ceph_assert(!pg->is_deleted());
 
-  // write info only at the end.  this is necessary because we check
-  // whether the past_intervals go far enough back or forward in time,
-  // but we don't check for holes.  we could avoid it by discarding
-  // the previous past_intervals and rebuilding from scratch, or we
-  // can just do this and commit all our work at the end.
-  ObjectStore::Transaction t;
-  int num = 0;
-  for (map<PG*,pistate>::iterator i = pis.begin(); i != pis.end(); ++i) {
-    PG *pg = i->first;
-    pg->lock();
-    pg->dirty_big_info = true;
-    pg->dirty_info = true;
-    pg->write_if_dirty(t);
-    pg->unlock();
+  pg->init(
+    role,
+    up,
+    up_primary,
+    acting,
+    acting_primary,
+    info->history,
+    info->past_intervals,
+    false,
+    rctx.transaction);
 
-    // don't let the transaction get too big
-    if (++num >= cct->_conf->osd_target_transaction_size) {
-      store->apply_transaction(service.meta_osr.get(), std::move(t));
-      t = ObjectStore::Transaction();
-      num = 0;
-    }
+  pg->init_collection_pool_opts();
+
+  if (pg->is_primary()) {
+    std::lock_guard locker{m_perf_queries_lock};
+    pg->set_dynamic_perf_stats_queries(m_perf_queries);
   }
-  if (!t.empty())
-    store->apply_transaction(service.meta_osr.get(), std::move(t));
+
+  pg->handle_initialize(rctx);
+  pg->handle_activate_map(rctx);
+
+  dispatch_context(rctx, pg.get(), osdmap, nullptr);
+
+  dout(10) << __func__ << " new pg " << *pg << dendl;
+  return pg;
 }
 
-/*
- * look up a pg.  if we have it, great.  if not, consider creating it IF the pg mapping
- * hasn't changed since the given epoch and we are the primary.
- */
-int OSD::handle_pg_peering_evt(
-  spg_t pgid,
-  const pg_history_t& orig_history,
-  const PastIntervals& pi,
-  epoch_t epoch,
-  PG::CephPeeringEvtRef evt)
+bool OSD::maybe_wait_for_max_pg(const OSDMapRef& osdmap,
+                               spg_t pgid,
+                               bool is_mon_create)
 {
-  if (service.splitting(pgid)) {
-    peering_wait_for_split[pgid].push_back(evt);
-    return -EEXIST;
+  const auto max_pgs_per_osd =
+    (cct->_conf.get_val<uint64_t>("mon_max_pg_per_osd") *
+     cct->_conf.get_val<double>("osd_max_pg_per_osd_hard_ratio"));
+
+  if (num_pgs < max_pgs_per_osd) {
+    return false;
   }
 
-  PG *pg = _lookup_lock_pg(pgid);
-  if (!pg) {
-    // same primary?
-    if (!osdmap->have_pg_pool(pgid.pool()))
-      return -EINVAL;
-    int up_primary, acting_primary;
-    vector<int> up, acting;
-    osdmap->pg_to_up_acting_osds(
-      pgid.pgid, &up, &up_primary, &acting, &acting_primary);
+  std::lock_guard l(pending_creates_lock);
+  if (is_mon_create) {
+    pending_creates_from_mon++;
+  } else {
+    bool is_primary = osdmap->get_pg_acting_role(pgid, whoami) == 0;
+    pending_creates_from_osd.emplace(pgid, is_primary);
+  }
+  dout(1) << __func__ << " withhold creation of pg " << pgid
+         << ": " << num_pgs << " >= "<< max_pgs_per_osd << dendl;
+  return true;
+}
 
-    pg_history_t history = orig_history;
-    bool valid_history = project_pg_history(
-      pgid, history, epoch, up, up_primary, acting, acting_primary);
+// to re-trigger a peering, we have to twiddle the pg mapping a little bit,
+// see PG::should_restart_peering(). OSDMap::pg_to_up_acting_osds() will turn
+// to up set if pg_temp is empty. so an empty pg_temp won't work.
+static vector<int32_t> twiddle(const vector<int>& acting) {
+  if (acting.size() > 1) {
+    return {acting[0]};
+  } else {
+    vector<int32_t> twiddled(acting.begin(), acting.end());
+    twiddled.push_back(-1);
+    return twiddled;
+  }
+}
 
-    if (!valid_history || epoch < history.same_interval_since) {
-      dout(10) << __func__ << pgid << " acting changed in "
-              << history.same_interval_since << " (msg from " << epoch << ")"
-              << dendl;
-      return -EINVAL;
+void OSD::resume_creating_pg()
+{
+  bool do_sub_pg_creates = false;
+  bool have_pending_creates = false;
+  {
+    const auto max_pgs_per_osd =
+      (cct->_conf.get_val<uint64_t>("mon_max_pg_per_osd") *
+       cct->_conf.get_val<double>("osd_max_pg_per_osd_hard_ratio"));
+    if (max_pgs_per_osd <= num_pgs) {
+      // this could happen if admin decreases this setting before a PG is removed
+      return;
     }
-
-    if (service.splitting(pgid)) {
-      ceph_abort();
+    unsigned spare_pgs = max_pgs_per_osd - num_pgs;
+    std::lock_guard l(pending_creates_lock);
+    if (pending_creates_from_mon > 0) {
+      dout(20) << __func__ << " pending_creates_from_mon "
+              << pending_creates_from_mon << dendl;
+      do_sub_pg_creates = true;
+      if (pending_creates_from_mon >= spare_pgs) {
+       spare_pgs = pending_creates_from_mon = 0;
+      } else {
+       spare_pgs -= pending_creates_from_mon;
+       pending_creates_from_mon = 0;
+      }
+    }
+    auto pg = pending_creates_from_osd.cbegin();
+    while (spare_pgs > 0 && pg != pending_creates_from_osd.cend()) {
+      dout(20) << __func__ << " pg " << pg->first << dendl;
+      vector<int> acting;
+      get_osdmap()->pg_to_up_acting_osds(pg->first.pgid, nullptr, nullptr, &acting, nullptr);
+      service.queue_want_pg_temp(pg->first.pgid, twiddle(acting), true);
+      pg = pending_creates_from_osd.erase(pg);
+      do_sub_pg_creates = true;
+      spare_pgs--;
     }
+    have_pending_creates = (pending_creates_from_mon > 0 ||
+                           !pending_creates_from_osd.empty());
+  }
 
-    // do we need to resurrect a deleting pg?
-    spg_t resurrected;
-    PGRef old_pg_state;
-    res_result result = _try_resurrect_pg(
-      service.get_osdmap(),
-      pgid,
-      &resurrected,
-      &old_pg_state);
-
-    PG::RecoveryCtx rctx = create_context();
-    switch (result) {
-    case RES_NONE: {
-      const pg_pool_t* pp = osdmap->get_pg_pool(pgid.pool());
-      if (pp->has_flag(pg_pool_t::FLAG_EC_OVERWRITES) &&
-         store->get_type() != "bluestore") {
-       clog->warn() << "pg " << pgid
-                    << " is at risk of silent data corruption: "
-                    << "the pool allows ec overwrites but is not stored in "
-                    << "bluestore, so deep scrubbing will not detect bitrot";
-      }
-      PG::_create(*rctx.transaction, pgid, pgid.get_split_bits(pp->get_pg_num()));
-      PG::_init(*rctx.transaction, pgid, pp);
-
-      int role = osdmap->calc_pg_role(whoami, acting, acting.size());
-      if (!pp->is_replicated() && role != pgid.shard)
-       role = -1;
-
-      pg = _create_lock_pg(
-       get_map(epoch),
-       pgid, false, false,
-       role,
-       up, up_primary,
-       acting, acting_primary,
-       history, pi,
-       *rctx.transaction);
-      pg->handle_create(&rctx);
-      pg->write_if_dirty(*rctx.transaction);
-      dispatch_context(rctx, pg, osdmap);
-
-      dout(10) << *pg << " is new" << dendl;
-
-      pg->queue_peering_event(evt);
-      wake_pg_waiters(pg);
-      pg->unlock();
-      return 0;
-    }
-    case RES_SELF: {
-      old_pg_state->lock();
-      OSDMapRef old_osd_map = old_pg_state->get_osdmap();
-      int old_role = old_pg_state->role;
-      vector<int> old_up = old_pg_state->up;
-      int old_up_primary = old_pg_state->up_primary.osd;
-      vector<int> old_acting = old_pg_state->acting;
-      int old_primary = old_pg_state->primary.osd;
-      pg_history_t old_history = old_pg_state->info.history;
-      PastIntervals old_past_intervals = old_pg_state->past_intervals;
-      old_pg_state->unlock();
-      pg = _create_lock_pg(
-       old_osd_map,
-       resurrected,
-       false,
-       true,
-       old_role,
-       old_up,
-       old_up_primary,
-       old_acting,
-       old_primary,
-       old_history,
-       old_past_intervals,
-       *rctx.transaction);
-      pg->handle_create(&rctx);
-      pg->write_if_dirty(*rctx.transaction);
-      dispatch_context(rctx, pg, osdmap);
-
-      dout(10) << *pg << " is new (resurrected)" << dendl;
-
-      pg->queue_peering_event(evt);
-      wake_pg_waiters(pg);
-      pg->unlock();
-      return 0;
-    }
-    case RES_PARENT: {
-      assert(old_pg_state);
-      old_pg_state->lock();
-      OSDMapRef old_osd_map = old_pg_state->get_osdmap();
-      int old_role = old_pg_state->role;
-      vector<int> old_up = old_pg_state->up;
-      int old_up_primary = old_pg_state->up_primary.osd;
-      vector<int> old_acting = old_pg_state->acting;
-      int old_primary = old_pg_state->primary.osd;
-      pg_history_t old_history = old_pg_state->info.history;
-      PastIntervals old_past_intervals = old_pg_state->past_intervals;
-      old_pg_state->unlock();
-      PG *parent = _create_lock_pg(
-       old_osd_map,
-       resurrected,
-       false,
-       true,
-       old_role,
-       old_up,
-       old_up_primary,
-       old_acting,
-       old_primary,
-       old_history,
-       old_past_intervals,
-       *rctx.transaction
-       );
-      parent->handle_create(&rctx);
-      parent->write_if_dirty(*rctx.transaction);
-      dispatch_context(rctx, parent, osdmap);
-
-      dout(10) << *parent << " is new" << dendl;
-
-      assert(service.splitting(pgid));
-      peering_wait_for_split[pgid].push_back(evt);
-
-      //parent->queue_peering_event(evt);
-      parent->queue_null(osdmap->get_epoch(), osdmap->get_epoch());
-      wake_pg_waiters(parent);
-      parent->unlock();
-      return 0;
-    }
-    default:
-      assert(0);
-      return 0;
+  bool do_renew_subs = false;
+  if (do_sub_pg_creates) {
+    if (monc->sub_want("osd_pg_creates", last_pg_create_epoch, 0)) {
+      dout(4) << __func__ << ": resolicit pg creates from mon since "
+             << last_pg_create_epoch << dendl;
+      do_renew_subs = true;
     }
-  } else {
-    // already had it.  did the mapping change?
-    if (epoch < pg->info.history.same_interval_since) {
-      dout(10) << *pg << __func__ << " acting changed in "
-              << pg->info.history.same_interval_since
-              << " (msg from " << epoch << ")" << dendl;
-    } else {
-      pg->queue_peering_event(evt);
+  }
+  version_t start = get_osdmap_epoch() + 1;
+  if (have_pending_creates) {
+    // don't miss any new osdmap deleting PGs
+    if (monc->sub_want("osdmap", start, 0)) {
+      dout(4) << __func__ << ": resolicit osdmap from mon since "
+             << start << dendl;
+      do_renew_subs = true;
+    }
+  } else if (do_sub_pg_creates) {
+    // no need to subscribe the osdmap continuously anymore
+    // once the pgtemp and/or mon_subscribe(pg_creates) is sent
+    if (monc->sub_want_increment("osdmap", start, CEPH_SUBSCRIBE_ONETIME)) {
+      dout(4) << __func__ << ": re-subscribe osdmap(onetime) since "
+             << start << dendl;
+      do_renew_subs = true;
     }
-    pg->unlock();
-    return -EEXIST;
   }
-}
 
+  if (do_renew_subs) {
+    monc->renew_subs();
+  }
+
+  service.send_pg_temp();
+}
 
 void OSD::build_initial_pg_history(
   spg_t pgid,
@@ -4163,13 +5062,7 @@ void OSD::build_initial_pg_history(
   PastIntervals *pi)
 {
   dout(10) << __func__ << " " << pgid << " created " << created << dendl;
-  h->epoch_created = created;
-  h->same_interval_since = created;
-  h->same_up_since = created;
-  h->same_primary_since = created;
-  h->last_scrub_stamp = created_stamp;
-  h->last_deep_scrub_stamp = created_stamp;
-  h->last_clean_scrub_stamp = created_stamp;
+  *h = pg_history_t(created, created_stamp);
 
   OSDMapRef lastmap = service.get_map(created);
   int up_primary, acting_primary;
@@ -4178,7 +5071,7 @@ void OSD::build_initial_pg_history(
     pgid.pgid, &up, &up_primary, &acting, &acting_primary);
 
   ostringstream debug;
-  for (epoch_t e = created + 1; e <= osdmap->get_epoch(); ++e) {
+  for (epoch_t e = created + 1; e <= get_osdmap_epoch(); ++e) {
     OSDMapRef osdmap = service.get_map(e);
     int new_up_primary, new_acting_primary;
     vector<int> new_up, new_acting;
@@ -4191,7 +5084,7 @@ void OSD::build_initial_pg_history(
       bool operator()(const set<pg_shard_t> &have) const {
        return have.size() >= pi->min_size;
       }
-      min_size_predicate_t(const pg_pool_t *i) : pi(i) {}
+      explicit min_size_predicate_t(const pg_pool_t *i) : pi(i) {}
     } min_size_predicate(osdmap->get_pg_pool(pgid.pgid.pool()));
 
     bool new_interval = PastIntervals::check_new_interval(
@@ -4203,20 +5096,29 @@ void OSD::build_initial_pg_history(
       up, new_up,
       h->same_interval_since,
       h->last_epoch_clean,
-      osdmap,
-      lastmap,
+      osdmap.get(),
+      lastmap.get(),
       pgid.pgid,
-      &min_size_predicate,
+      min_size_predicate,
       pi,
       &debug);
     if (new_interval) {
       h->same_interval_since = e;
-    }
-    if (up != new_up) {
-      h->same_up_since = e;
-    }
-    if (acting_primary != new_acting_primary) {
-      h->same_primary_since = e;
+      if (up != new_up) {
+        h->same_up_since = e;
+      }
+      if (acting_primary != new_acting_primary) {
+        h->same_primary_since = e;
+      }
+      if (pgid.pgid.is_split(lastmap->get_pg_num(pgid.pgid.pool()),
+                             osdmap->get_pg_num(pgid.pgid.pool()),
+                             nullptr)) {
+        h->last_epoch_split = e;
+      }
+      up = new_up;
+      acting = new_acting;
+      up_primary = new_up_primary;
+      acting_primary = new_acting_primary;
     }
     lastmap = osdmap;
   }
@@ -4227,102 +5129,6 @@ void OSD::build_initial_pg_history(
           << dendl;
 }
 
-/**
- * Fill in the passed history so you know same_interval_since, same_up_since,
- * and same_primary_since.
- */
-bool OSD::project_pg_history(spg_t pgid, pg_history_t& h, epoch_t from,
-                            const vector<int>& currentup,
-                            int currentupprimary,
-                            const vector<int>& currentacting,
-                            int currentactingprimary)
-{
-  dout(15) << "project_pg_history " << pgid
-           << " from " << from << " to " << osdmap->get_epoch()
-           << ", start " << h
-           << dendl;
-
-  epoch_t e;
-  for (e = osdmap->get_epoch();
-       e > from;
-       e--) {
-    // verify during intermediate epoch (e-1)
-    OSDMapRef oldmap = service.try_get_map(e-1);
-    if (!oldmap) {
-      dout(15) << __func__ << ": found map gap, returning false" << dendl;
-      return false;
-    }
-    assert(oldmap->have_pg_pool(pgid.pool()));
-
-    int upprimary, actingprimary;
-    vector<int> up, acting;
-    oldmap->pg_to_up_acting_osds(
-      pgid.pgid,
-      &up,
-      &upprimary,
-      &acting,
-      &actingprimary);
-
-    // acting set change?
-    if ((actingprimary != currentactingprimary ||
-        upprimary != currentupprimary ||
-        acting != currentacting ||
-        up != currentup) && e > h.same_interval_since) {
-      dout(15) << "project_pg_history " << pgid << " acting|up changed in " << e
-              << " from " << acting << "/" << up
-              << " " << actingprimary << "/" << upprimary
-              << " -> " << currentacting << "/" << currentup
-              << " " << currentactingprimary << "/" << currentupprimary
-              << dendl;
-      h.same_interval_since = e;
-    }
-    // split?
-    if (pgid.is_split(oldmap->get_pg_num(pgid.pool()),
-                     osdmap->get_pg_num(pgid.pool()),
-                     0) && e > h.same_interval_since) {
-      h.same_interval_since = e;
-    }
-    // up set change?
-    if ((up != currentup || upprimary != currentupprimary)
-       && e > h.same_up_since) {
-      dout(15) << "project_pg_history " << pgid << " up changed in " << e
-              << " from " << up << " " << upprimary
-              << " -> " << currentup << " " << currentupprimary << dendl;
-      h.same_up_since = e;
-    }
-
-    // primary change?
-    if (OSDMap::primary_changed(
-         actingprimary,
-         acting,
-         currentactingprimary,
-         currentacting) &&
-        e > h.same_primary_since) {
-      dout(15) << "project_pg_history " << pgid << " primary changed in " << e << dendl;
-      h.same_primary_since = e;
-    }
-
-    if (h.same_interval_since >= e && h.same_up_since >= e && h.same_primary_since >= e)
-      break;
-  }
-
-  // base case: these floors should be the creation epoch if we didn't
-  // find any changes.
-  if (e == h.epoch_created) {
-    if (!h.same_interval_since)
-      h.same_interval_since = e;
-    if (!h.same_up_since)
-      h.same_up_since = e;
-    if (!h.same_primary_since)
-      h.same_primary_since = e;
-  }
-
-  dout(15) << "project_pg_history end " << h << dendl;
-  return true;
-}
-
-
-
 void OSD::_add_heartbeat_peer(int p)
 {
   if (p == whoami)
@@ -4331,46 +5137,48 @@ void OSD::_add_heartbeat_peer(int p)
 
   map<int,HeartbeatInfo>::iterator i = heartbeat_peers.find(p);
   if (i == heartbeat_peers.end()) {
-    pair<ConnectionRef,ConnectionRef> cons = service.get_con_osd_hb(p, osdmap->get_epoch());
+    pair<ConnectionRef,ConnectionRef> cons = service.get_con_osd_hb(p, get_osdmap_epoch());
     if (!cons.first)
       return;
+    assert(cons.second);
+
     hi = &heartbeat_peers[p];
     hi->peer = p;
-    HeartbeatSession *s = new HeartbeatSession(p);
+
+    auto stamps = service.get_hb_stamps(p);
+
+    auto sb = ceph::make_ref<Session>(cct, cons.first.get());
+    sb->peer = p;
+    sb->stamps = stamps;
+    hi->hb_interval_start = ceph_clock_now();
     hi->con_back = cons.first.get();
-    hi->con_back->set_priv(s->get());
-    if (cons.second) {
-      hi->con_front = cons.second.get();
-      hi->con_front->set_priv(s->get());
-      dout(10) << "_add_heartbeat_peer: new peer osd." << p
-              << " " << hi->con_back->get_peer_addr()
-              << " " << hi->con_front->get_peer_addr()
-              << dendl;
-    } else {
-      hi->con_front.reset(NULL);
-      dout(10) << "_add_heartbeat_peer: new peer osd." << p
-              << " " << hi->con_back->get_peer_addr()
-              << dendl;
-    }
-    s->put();
+    hi->con_back->set_priv(sb);
+
+    auto sf = ceph::make_ref<Session>(cct, cons.second.get());
+    sf->peer = p;
+    sf->stamps = stamps;
+    hi->con_front = cons.second.get();
+    hi->con_front->set_priv(sf);
+
+    dout(10) << "_add_heartbeat_peer: new peer osd." << p
+            << " " << hi->con_back->get_peer_addr()
+            << " " << hi->con_front->get_peer_addr()
+            << dendl;
   } else {
     hi = &i->second;
   }
-  hi->epoch = osdmap->get_epoch();
+  hi->epoch = get_osdmap_epoch();
 }
 
 void OSD::_remove_heartbeat_peer(int n)
 {
   map<int,HeartbeatInfo>::iterator q = heartbeat_peers.find(n);
-  assert(q != heartbeat_peers.end());
+  ceph_assert(q != heartbeat_peers.end());
   dout(20) << " removing heartbeat peer osd." << n
           << " " << q->second.con_back->get_peer_addr()
           << " " << (q->second.con_front ? q->second.con_front->get_peer_addr() : entity_addr_t())
           << dendl;
-  q->second.con_back->mark_down();
-  if (q->second.con_front) {
-    q->second.con_front->mark_down();
-  }
+  q->second.clear_mark_down();
   heartbeat_peers.erase(q);
 }
 
@@ -4384,9 +5192,9 @@ void OSD::need_heartbeat_peer_update()
 
 void OSD::maybe_update_heartbeat_peers()
 {
-  assert(osd_lock.is_locked());
+  ceph_assert(ceph_mutex_is_locked(osd_lock));
 
-  if (is_waiting_for_healthy()) {
+  if (is_waiting_for_healthy() || is_active()) {
     utime_t now = ceph_clock_now();
     if (last_heartbeat_resample == utime_t()) {
       last_heartbeat_resample = now;
@@ -4397,7 +5205,9 @@ void OSD::maybe_update_heartbeat_peers()
        dout(10) << "maybe_update_heartbeat_peers forcing update after " << dur << " seconds" << dendl;
        heartbeat_set_peers_need_update();
        last_heartbeat_resample = now;
-       reset_heartbeat_peers();   // we want *new* peers!
+       // automatically clean up any stale heartbeat peers
+       // if we are unhealthy, then clean all
+       reset_heartbeat_peers(is_waiting_for_healthy());
       }
     }
   }
@@ -4406,43 +5216,41 @@ void OSD::maybe_update_heartbeat_peers()
     return;
   heartbeat_clear_peers_need_update();
 
-  Mutex::Locker l(heartbeat_lock);
+  std::lock_guard l(heartbeat_lock);
 
   dout(10) << "maybe_update_heartbeat_peers updating" << dendl;
 
 
   // build heartbeat from set
   if (is_active()) {
-    RWLock::RLocker l(pg_map_lock);
-    for (ceph::unordered_map<spg_t, PG*>::iterator i = pg_map.begin();
-        i != pg_map.end();
-        ++i) {
-      PG *pg = i->second;
-      pg->heartbeat_peer_lock.Lock();
-      dout(20) << i->first << " heartbeat_peers " << pg->heartbeat_peers << dendl;
-      for (set<int>::iterator p = pg->heartbeat_peers.begin();
-          p != pg->heartbeat_peers.end();
-          ++p)
-       if (osdmap->is_up(*p))
-         _add_heartbeat_peer(*p);
-      for (set<int>::iterator p = pg->probe_targets.begin();
-          p != pg->probe_targets.end();
-          ++p)
-       if (osdmap->is_up(*p))
-         _add_heartbeat_peer(*p);
-      pg->heartbeat_peer_lock.Unlock();
+    vector<PGRef> pgs;
+    _get_pgs(&pgs);
+    for (auto& pg : pgs) {
+      pg->with_heartbeat_peers([&](int peer) {
+         if (get_osdmap()->is_up(peer)) {
+           _add_heartbeat_peer(peer);
+         }
+       });
     }
   }
 
   // include next and previous up osds to ensure we have a fully-connected set
   set<int> want, extras;
-  int next = osdmap->get_next_up_osd_after(whoami);
+  const int next = get_osdmap()->get_next_up_osd_after(whoami);
   if (next >= 0)
     want.insert(next);
-  int prev = osdmap->get_previous_up_osd_before(whoami);
+  int prev = get_osdmap()->get_previous_up_osd_before(whoami);
   if (prev >= 0 && prev != next)
     want.insert(prev);
 
+  // make sure we have at least **min_down** osds coming from different
+  // subtree level (e.g., hosts) for fast failure detection.
+  auto min_down = cct->_conf.get_val<uint64_t>("mon_osd_min_down_reporters");
+  auto subtree = cct->_conf.get_val<string>("mon_osd_reporter_subtree_level");
+  auto limit = std::max(min_down, (uint64_t)cct->_conf->osd_heartbeat_min_peers);
+  get_osdmap()->get_random_up_osds_by_subtree(
+    whoami, subtree, limit, want, &want);
+
   for (set<int>::iterator p = want.begin(); p != want.end(); ++p) {
     dout(10) << " adding neighbor peer osd." << *p << dendl;
     extras.insert(*p);
@@ -4452,21 +5260,20 @@ void OSD::maybe_update_heartbeat_peers()
   // remove down peers; enumerate extras
   map<int,HeartbeatInfo>::iterator p = heartbeat_peers.begin();
   while (p != heartbeat_peers.end()) {
-    if (!osdmap->is_up(p->first)) {
+    if (!get_osdmap()->is_up(p->first)) {
       int o = p->first;
       ++p;
       _remove_heartbeat_peer(o);
       continue;
     }
-    if (p->second.epoch < osdmap->get_epoch()) {
+    if (p->second.epoch < get_osdmap_epoch()) {
       extras.insert(p->first);
     }
     ++p;
   }
 
   // too few?
-  int start = osdmap->get_next_up_osd_after(whoami);
-  for (int n = start; n >= 0; ) {
+  for (int n = next; n >= 0; ) {
     if ((int)heartbeat_peers.size() >= cct->_conf->osd_heartbeat_min_peers)
       break;
     if (!extras.count(n) && !want.count(n) && n != whoami) {
@@ -4474,8 +5281,8 @@ void OSD::maybe_update_heartbeat_peers()
       extras.insert(n);
       _add_heartbeat_peer(n);
     }
-    n = osdmap->get_next_up_osd_after(n);
-    if (n == start)
+    n = get_osdmap()->get_next_up_osd_after(n);
+    if (n == next)
       break;  // came full circle; stop
   }
 
@@ -4489,44 +5296,79 @@ void OSD::maybe_update_heartbeat_peers()
   }
 
   dout(10) << "maybe_update_heartbeat_peers " << heartbeat_peers.size() << " peers, extras " << extras << dendl;
+
+  // clean up stale failure pending
+  for (auto it = failure_pending.begin(); it != failure_pending.end();) {
+    if (heartbeat_peers.count(it->first) == 0) {
+      send_still_alive(get_osdmap_epoch(), it->first, it->second.second);
+      failure_pending.erase(it++);
+    } else {
+      it++;
+    }
+  }
 }
 
-void OSD::reset_heartbeat_peers()
+void OSD::reset_heartbeat_peers(bool all)
 {
-  assert(osd_lock.is_locked());
+  ceph_assert(ceph_mutex_is_locked(osd_lock));
   dout(10) << "reset_heartbeat_peers" << dendl;
-  Mutex::Locker l(heartbeat_lock);
-  while (!heartbeat_peers.empty()) {
-    HeartbeatInfo& hi = heartbeat_peers.begin()->second;
-    hi.con_back->mark_down();
-    if (hi.con_front) {
-      hi.con_front->mark_down();
+  utime_t stale = ceph_clock_now();
+  stale -= cct->_conf.get_val<int64_t>("osd_heartbeat_stale");
+  std::lock_guard l(heartbeat_lock);
+  for (auto it = heartbeat_peers.begin(); it != heartbeat_peers.end();) {
+    auto& [peer, hi] = *it;
+    if (all || hi.is_stale(stale)) {
+      hi.clear_mark_down();
+      // stop sending failure_report to mon too
+      failure_queue.erase(peer);
+      failure_pending.erase(peer);
+      it = heartbeat_peers.erase(it);
+    } else {
+      ++it;
     }
-    heartbeat_peers.erase(heartbeat_peers.begin());
   }
-  failure_queue.clear();
 }
 
 void OSD::handle_osd_ping(MOSDPing *m)
 {
   if (superblock.cluster_fsid != m->fsid) {
     dout(20) << "handle_osd_ping from " << m->get_source_inst()
-            << " bad fsid " << m->fsid << " != " << superblock.cluster_fsid << dendl;
+            << " bad fsid " << m->fsid << " != " << superblock.cluster_fsid
+            << dendl;
     m->put();
     return;
   }
 
   int from = m->get_source().num();
 
-  heartbeat_lock.Lock();
+  heartbeat_lock.lock();
   if (is_stopping()) {
-    heartbeat_lock.Unlock();
+    heartbeat_lock.unlock();
     m->put();
     return;
   }
 
+  utime_t now = ceph_clock_now();
+  auto mnow = service.get_mnow();
+  ConnectionRef con(m->get_connection());
   OSDMapRef curmap = service.get_osdmap();
-  assert(curmap);
+  if (!curmap) {
+    heartbeat_lock.unlock();
+    m->put();
+    return;
+  }
+
+  auto sref = con->get_priv();
+  Session *s = static_cast<Session*>(sref.get());
+  if (!s) {
+    heartbeat_lock.unlock();
+    m->put();
+    return;
+  }
+  if (!s->stamps) {
+    s->peer = from;
+    s->stamps = service.get_hb_stamps(from);
+  }
 
   switch (m->op) {
 
@@ -4556,23 +5398,38 @@ void OSD::handle_osd_ping(MOSDPing *m)
        }
       }
 
+      ceph::signedspan sender_delta_ub{};
+      s->stamps->got_ping(
+       m->up_from,
+       mnow,
+       m->mono_send_stamp,
+       m->delta_ub,
+       &sender_delta_ub);
+      dout(20) << __func__ << " new stamps " << *s->stamps << dendl;
+
       if (!cct->get_heartbeat_map()->is_healthy()) {
-       dout(10) << "internal heartbeat not healthy, dropping ping request" << dendl;
+       dout(10) << "internal heartbeat not healthy, dropping ping request"
+                << dendl;
        break;
       }
 
       Message *r = new MOSDPing(monc->get_fsid(),
                                curmap->get_epoch(),
                                MOSDPing::PING_REPLY,
-                               m->stamp);
-      m->get_connection()->send_message(r);
+                               m->ping_stamp,
+                               m->mono_ping_stamp,
+                               mnow,
+                               service.get_up_epoch(),
+                               cct->_conf->osd_heartbeat_min_size,
+                               sender_delta_ub);
+      con->send_message(r);
 
       if (curmap->is_up(from)) {
-       service.note_peer_epoch(from, m->map_epoch);
        if (is_active()) {
-         ConnectionRef con = service.get_con_osd_cluster(from, curmap->get_epoch());
-         if (con) {
-           service.share_map_peer(from, con.get());
+         ConnectionRef cluster_con = service.get_con_osd_cluster(
+           from, curmap->get_epoch());
+         if (cluster_con) {
+           service.maybe_share_map(cluster_con.get(), curmap, m->map_epoch);
          }
        }
       } else if (!curmap->exists(from) ||
@@ -4581,8 +5438,12 @@ void OSD::handle_osd_ping(MOSDPing *m)
        Message *r = new MOSDPing(monc->get_fsid(),
                                  curmap->get_epoch(),
                                  MOSDPing::YOU_DIED,
-                                 m->stamp);
-       m->get_connection()->send_message(r);
+                                 m->ping_stamp,
+                                 m->mono_ping_stamp,
+                                 mnow,
+                                 service.get_up_epoch(),
+                                 cct->_conf->osd_heartbeat_min_size);
+       con->send_message(r);
       }
     }
     break;
@@ -4591,59 +5452,217 @@ void OSD::handle_osd_ping(MOSDPing *m)
     {
       map<int,HeartbeatInfo>::iterator i = heartbeat_peers.find(from);
       if (i != heartbeat_peers.end()) {
-       if (m->get_connection() == i->second.con_back) {
-         dout(25) << "handle_osd_ping got reply from osd." << from
-                  << " first_tx " << i->second.first_tx
-                  << " last_tx " << i->second.last_tx
-                  << " last_rx_back " << i->second.last_rx_back << " -> " << m->stamp
-                  << " last_rx_front " << i->second.last_rx_front
-                  << dendl;
-         i->second.last_rx_back = m->stamp;
-         // if there is no front con, set both stamps.
-         if (i->second.con_front == NULL)
-           i->second.last_rx_front = m->stamp;
-       } else if (m->get_connection() == i->second.con_front) {
-         dout(25) << "handle_osd_ping got reply from osd." << from
-                  << " first_tx " << i->second.first_tx
-                  << " last_tx " << i->second.last_tx
-                  << " last_rx_back " << i->second.last_rx_back
-                  << " last_rx_front " << i->second.last_rx_front << " -> " << m->stamp
-                  << dendl;
-         i->second.last_rx_front = m->stamp;
-       }
+        auto acked = i->second.ping_history.find(m->ping_stamp);
+        if (acked != i->second.ping_history.end()) {
+          int &unacknowledged = acked->second.second;
+          if (con == i->second.con_back) {
+            dout(25) << "handle_osd_ping got reply from osd." << from
+                     << " first_tx " << i->second.first_tx
+                     << " last_tx " << i->second.last_tx
+                     << " last_rx_back " << i->second.last_rx_back
+                    << " -> " << now
+                     << " last_rx_front " << i->second.last_rx_front
+                     << dendl;
+            i->second.last_rx_back = now;
+            ceph_assert(unacknowledged > 0);
+            --unacknowledged;
+            // if there is no front con, set both stamps.
+            if (i->second.con_front == NULL) {
+              i->second.last_rx_front = now;
+              ceph_assert(unacknowledged > 0);
+              --unacknowledged;
+            }
+          } else if (con == i->second.con_front) {
+            dout(25) << "handle_osd_ping got reply from osd." << from
+                     << " first_tx " << i->second.first_tx
+                     << " last_tx " << i->second.last_tx
+                     << " last_rx_back " << i->second.last_rx_back
+                     << " last_rx_front " << i->second.last_rx_front
+                    << " -> " << now
+                     << dendl;
+            i->second.last_rx_front = now;
+            ceph_assert(unacknowledged > 0);
+            --unacknowledged;
+          }
 
-        utime_t cutoff = ceph_clock_now();
-        cutoff -= cct->_conf->osd_heartbeat_grace;
-        if (i->second.is_healthy(cutoff)) {
-          // Cancel false reports
-         auto failure_queue_entry = failure_queue.find(from);
-         if (failure_queue_entry != failure_queue.end()) {
-            dout(10) << "handle_osd_ping canceling queued "
-                     << "failure report for osd." << from << dendl;
-            failure_queue.erase(failure_queue_entry);
+          if (unacknowledged == 0) {
+            // succeeded in getting all replies
+            dout(25) << "handle_osd_ping got all replies from osd." << from
+                     << " , erase pending ping(sent at " << m->ping_stamp << ")"
+                     << " and older pending ping(s)"
+                     << dendl;
+
+#define ROUND_S_TO_USEC(sec) (uint32_t)((sec) * 1000 * 1000 + 0.5)
+           ++i->second.hb_average_count;
+           uint32_t back_pingtime = ROUND_S_TO_USEC(i->second.last_rx_back - m->ping_stamp);
+           i->second.hb_total_back += back_pingtime;
+           if (back_pingtime < i->second.hb_min_back)
+             i->second.hb_min_back = back_pingtime;
+           if (back_pingtime > i->second.hb_max_back)
+             i->second.hb_max_back = back_pingtime;
+           uint32_t front_pingtime = ROUND_S_TO_USEC(i->second.last_rx_front - m->ping_stamp);
+           i->second.hb_total_front += front_pingtime;
+           if (front_pingtime < i->second.hb_min_front)
+             i->second.hb_min_front = front_pingtime;
+           if (front_pingtime > i->second.hb_max_front)
+             i->second.hb_max_front = front_pingtime;
+
+           ceph_assert(i->second.hb_interval_start != utime_t());
+           if (i->second.hb_interval_start == utime_t())
+             i->second.hb_interval_start = now;
+           int64_t hb_avg_time_period = 60;
+           if (cct->_conf.get_val<int64_t>("debug_heartbeat_testing_span")) {
+             hb_avg_time_period = cct->_conf.get_val<int64_t>("debug_heartbeat_testing_span");
+           }
+           if (now - i->second.hb_interval_start >=  utime_t(hb_avg_time_period, 0)) {
+              uint32_t back_avg = i->second.hb_total_back / i->second.hb_average_count;
+              uint32_t back_min = i->second.hb_min_back;
+              uint32_t back_max = i->second.hb_max_back;
+              uint32_t front_avg = i->second.hb_total_front / i->second.hb_average_count;
+              uint32_t front_min = i->second.hb_min_front;
+              uint32_t front_max = i->second.hb_max_front;
+
+             // Reset for new interval
+             i->second.hb_average_count = 0;
+             i->second.hb_interval_start = now;
+             i->second.hb_total_back = i->second.hb_max_back = 0;
+             i->second.hb_min_back =  UINT_MAX;
+             i->second.hb_total_front = i->second.hb_max_front = 0;
+             i->second.hb_min_front = UINT_MAX;
+
+             // Record per osd interace ping times
+             // Based on osd_heartbeat_interval ignoring that it is randomly short than this interval
+             if (i->second.hb_back_pingtime.size() == 0) {
+               ceph_assert(i->second.hb_front_pingtime.size() == 0);
+               for (unsigned k = 0 ; k < hb_vector_size; ++k) {
+                 i->second.hb_back_pingtime.push_back(back_avg);
+                 i->second.hb_back_min.push_back(back_min);
+                 i->second.hb_back_max.push_back(back_max);
+                 i->second.hb_front_pingtime.push_back(front_avg);
+                 i->second.hb_front_min.push_back(front_min);
+                 i->second.hb_front_max.push_back(front_max);
+                 ++i->second.hb_index;
+               }
+             } else {
+               int index = i->second.hb_index & (hb_vector_size - 1);
+               i->second.hb_back_pingtime[index] = back_avg;
+               i->second.hb_back_min[index] = back_min;
+               i->second.hb_back_max[index] = back_max;
+               i->second.hb_front_pingtime[index] = front_avg;
+               i->second.hb_front_min[index] = front_min;
+               i->second.hb_front_max[index] = front_max;
+               ++i->second.hb_index;
+             }
+
+             {
+               std::lock_guard l(service.stat_lock);
+               service.osd_stat.hb_pingtime[from].last_update = now.sec();
+               service.osd_stat.hb_pingtime[from].back_last =  back_pingtime;
+
+               uint32_t total = 0;
+               uint32_t min = UINT_MAX;
+               uint32_t max = 0;
+               uint32_t count = 0;
+               uint32_t which = 0;
+               uint32_t size = (uint32_t)i->second.hb_back_pingtime.size();
+               for (int32_t k = size - 1 ; k >= 0; --k) {
+                 ++count;
+                 int index = (i->second.hb_index + k) % size;
+                 total += i->second.hb_back_pingtime[index];
+                 if (i->second.hb_back_min[index] < min)
+                   min = i->second.hb_back_min[index];
+                 if (i->second.hb_back_max[index] > max)
+                   max = i->second.hb_back_max[index];
+                 if (count == 1 || count == 5 || count == 15) {
+                   service.osd_stat.hb_pingtime[from].back_pingtime[which] = total / count;
+                   service.osd_stat.hb_pingtime[from].back_min[which] = min;
+                   service.osd_stat.hb_pingtime[from].back_max[which] = max;
+                   which++;
+                   if (count == 15)
+                     break;
+                 }
+               }
+
+                if (i->second.con_front != NULL) {
+                 service.osd_stat.hb_pingtime[from].front_last = front_pingtime;
+
+                 total = 0;
+                 min = UINT_MAX;
+                 max = 0;
+                 count = 0;
+                 which = 0;
+                 for (int32_t k = size - 1 ; k >= 0; --k) {
+                   ++count;
+                   int index = (i->second.hb_index + k) % size;
+                   total += i->second.hb_front_pingtime[index];
+                   if (i->second.hb_front_min[index] < min)
+                     min = i->second.hb_front_min[index];
+                   if (i->second.hb_front_max[index] > max)
+                     max = i->second.hb_front_max[index];
+                   if (count == 1 || count == 5 || count == 15) {
+                     service.osd_stat.hb_pingtime[from].front_pingtime[which] = total / count;
+                     service.osd_stat.hb_pingtime[from].front_min[which] = min;
+                     service.osd_stat.hb_pingtime[from].front_max[which] = max;
+                     which++;
+                     if (count == 15)
+                       break;
+                   }
+                 }
+               }
+             }
+           } else {
+               std::lock_guard l(service.stat_lock);
+               service.osd_stat.hb_pingtime[from].back_last =  back_pingtime;
+                if (i->second.con_front != NULL)
+                 service.osd_stat.hb_pingtime[from].front_last = front_pingtime;
+           }
+            i->second.ping_history.erase(i->second.ping_history.begin(), ++acked);
           }
 
-         auto failure_pending_entry = failure_pending.find(from);
-         if (failure_pending_entry != failure_pending.end()) {
-            dout(10) << "handle_osd_ping canceling in-flight "
-                     << "failure report for osd." << from << dendl;
-            send_still_alive(curmap->get_epoch(),
-                            failure_pending_entry->second.second);
-            failure_pending.erase(failure_pending_entry);
+          if (i->second.is_healthy(now)) {
+            // Cancel false reports
+            auto failure_queue_entry = failure_queue.find(from);
+            if (failure_queue_entry != failure_queue.end()) {
+              dout(10) << "handle_osd_ping canceling queued "
+                       << "failure report for osd." << from << dendl;
+              failure_queue.erase(failure_queue_entry);
+            }
+
+            auto failure_pending_entry = failure_pending.find(from);
+            if (failure_pending_entry != failure_pending.end()) {
+              dout(10) << "handle_osd_ping canceling in-flight "
+                       << "failure report for osd." << from << dendl;
+              send_still_alive(curmap->get_epoch(),
+                               from,
+                               failure_pending_entry->second.second);
+              failure_pending.erase(failure_pending_entry);
+            }
           }
+        } else {
+          // old replies, deprecated by newly sent pings.
+          dout(10) << "handle_osd_ping no pending ping(sent at " << m->ping_stamp
+                   << ") is found, treat as covered by newly sent pings "
+                   << "and ignore"
+                   << dendl;
         }
       }
 
       if (m->map_epoch &&
          curmap->is_up(from)) {
-       service.note_peer_epoch(from, m->map_epoch);
        if (is_active()) {
-         ConnectionRef con = service.get_con_osd_cluster(from, curmap->get_epoch());
-         if (con) {
-           service.share_map_peer(from, con.get());
+         ConnectionRef cluster_con = service.get_con_osd_cluster(
+           from, curmap->get_epoch());
+         if (cluster_con) {
+           service.maybe_share_map(cluster_con.get(), curmap, m->map_epoch);
          }
        }
       }
+
+      s->stamps->got_ping_reply(
+       mnow,
+       m->mono_send_stamp,
+       m->delta_ub);
+      dout(20) << __func__ << " new stamps " << *s->stamps << dendl;
     }
     break;
 
@@ -4654,23 +5673,27 @@ void OSD::handle_osd_ping(MOSDPing *m)
     break;
   }
 
-  heartbeat_lock.Unlock();
+  heartbeat_lock.unlock();
   m->put();
 }
 
 void OSD::heartbeat_entry()
 {
-  Mutex::Locker l(heartbeat_lock);
+  std::unique_lock l(heartbeat_lock);
   if (is_stopping())
     return;
   while (!heartbeat_stop) {
     heartbeat();
 
-    double wait = .5 + ((float)(rand() % 10)/10.0) * (float)cct->_conf->osd_heartbeat_interval;
-    utime_t w;
-    w.set_from_double(wait);
+    double wait;
+    if (cct->_conf.get_val<bool>("debug_disable_randomized_ping")) {
+      wait = (float)cct->_conf->osd_heartbeat_interval;
+    } else {
+      wait = .5 + ((float)(rand() % 10)/10.0) * (float)cct->_conf->osd_heartbeat_interval;
+    }
+    auto w = ceph::make_timespan(wait);
     dout(30) << "heartbeat_entry sleeping for " << wait << dendl;
-    heartbeat_cond.WaitInterval(heartbeat_lock, w);
+    heartbeat_cond.wait_for(l, w);
     if (is_stopping())
       return;
     dout(30) << "heartbeat_entry woke up" << dendl;
@@ -4679,19 +5702,17 @@ void OSD::heartbeat_entry()
 
 void OSD::heartbeat_check()
 {
-  assert(heartbeat_lock.is_locked());
+  ceph_assert(ceph_mutex_is_locked(heartbeat_lock));
   utime_t now = ceph_clock_now();
 
-  // check for heartbeat replies (move me elsewhere?)
-  utime_t cutoff = now;
-  cutoff -= cct->_conf->osd_heartbeat_grace;
+  // check for incoming heartbeats (move me elsewhere?)
   for (map<int,HeartbeatInfo>::iterator p = heartbeat_peers.begin();
        p != heartbeat_peers.end();
        ++p) {
 
     if (p->second.first_tx == utime_t()) {
       dout(25) << "heartbeat_check we haven't sent ping to osd." << p->first
-               << "yet, skipping" << dendl;
+               << " yet, skipping" << dendl;
       continue;
     }
 
@@ -4701,21 +5722,28 @@ void OSD::heartbeat_check()
             << " last_rx_back " << p->second.last_rx_back
             << " last_rx_front " << p->second.last_rx_front
             << dendl;
-    if (p->second.is_unhealthy(cutoff)) {
+    if (p->second.is_unhealthy(now)) {
+      utime_t oldest_deadline = p->second.ping_history.begin()->second.first;
       if (p->second.last_rx_back == utime_t() ||
          p->second.last_rx_front == utime_t()) {
-       derr << "heartbeat_check: no reply from " << p->second.con_front->get_peer_addr().get_sockaddr()
-            << " osd." << p->first << " ever on either front or back, first ping sent "
-            << p->second.first_tx << " (cutoff " << cutoff << ")" << dendl;
+        derr << "heartbeat_check: no reply from "
+             << p->second.con_front->get_peer_addr().get_sockaddr()
+             << " osd." << p->first
+             << " ever on either front or back, first ping sent "
+             << p->second.first_tx
+             << " (oldest deadline " << oldest_deadline << ")"
+             << dendl;
        // fail
-       failure_queue[p->first] = p->second.last_tx;
+       failure_queue[p->first] = p->second.first_tx;
       } else {
-       derr << "heartbeat_check: no reply from " << p->second.con_front->get_peer_addr().get_sockaddr()
+       derr << "heartbeat_check: no reply from "
+             << p->second.con_front->get_peer_addr().get_sockaddr()
             << " osd." << p->first << " since back " << p->second.last_rx_back
             << " front " << p->second.last_rx_front
-            << " (cutoff " << cutoff << ")" << dendl;
+            << " (oldest deadline " << oldest_deadline << ")"
+             << dendl;
        // fail
-       failure_queue[p->first] = MIN(p->second.last_rx_back, p->second.last_rx_front);
+       failure_queue[p->first] = std::min(p->second.last_rx_back, p->second.last_rx_front);
       }
     }
   }
@@ -4723,11 +5751,19 @@ void OSD::heartbeat_check()
 
 void OSD::heartbeat()
 {
+  ceph_assert(ceph_mutex_is_locked_by_me(heartbeat_lock));
   dout(30) << "heartbeat" << dendl;
 
   // get CPU load avg
   double loadavgs[1];
-  int n_samples = 86400 / cct->_conf->osd_heartbeat_interval;
+  int hb_interval = cct->_conf->osd_heartbeat_interval;
+  int n_samples = 86400;
+  if (hb_interval > 1) {
+    n_samples /= hb_interval;
+    if (n_samples < 1)
+      n_samples = 1;
+  }
+
   if (getloadavg(loadavgs, 1) == 1) {
     logger->set(l_osd_loadavg, 100 * loadavgs[0]);
     daily_loadavg = (daily_loadavg * (n_samples - 1) + loadavgs[0]) / n_samples;
@@ -4736,37 +5772,72 @@ void OSD::heartbeat()
 
   dout(30) << "heartbeat checking stats" << dendl;
 
-  // refresh stats?
+  // refresh peer list and osd stats
   vector<int> hb_peers;
   for (map<int,HeartbeatInfo>::iterator p = heartbeat_peers.begin();
        p != heartbeat_peers.end();
        ++p)
     hb_peers.push_back(p->first);
-  service.update_osd_stat(hb_peers);
 
-  dout(5) << "heartbeat: " << service.get_osd_stat() << dendl;
+  auto new_stat = service.set_osd_stat(hb_peers, get_num_pgs());
+  dout(5) << __func__ << " " << new_stat << dendl;
+  ceph_assert(new_stat.statfs.total);
+
+  float pratio;
+  float ratio = service.compute_adjusted_ratio(new_stat, &pratio);
+
+  service.check_full_status(ratio, pratio);
 
   utime_t now = ceph_clock_now();
+  auto mnow = service.get_mnow();
+  utime_t deadline = now;
+  deadline += cct->_conf->osd_heartbeat_grace;
 
   // send heartbeats
   for (map<int,HeartbeatInfo>::iterator i = heartbeat_peers.begin();
        i != heartbeat_peers.end();
        ++i) {
     int peer = i->first;
+    Session *s = static_cast<Session*>(i->second.con_back->get_priv().get());
+    if (!s) {
+      dout(30) << "heartbeat osd." << peer << " has no open con" << dendl;
+      continue;
+    }
+    dout(30) << "heartbeat sending ping to osd." << peer << dendl;
+
     i->second.last_tx = now;
     if (i->second.first_tx == utime_t())
       i->second.first_tx = now;
-    dout(30) << "heartbeat sending ping to osd." << peer << dendl;
-    i->second.con_back->send_message(new MOSDPing(monc->get_fsid(),
-                                         service.get_osdmap()->get_epoch(),
-                                         MOSDPing::PING,
-                                         now));
+    i->second.ping_history[now] = make_pair(deadline,
+      HeartbeatInfo::HEARTBEAT_MAX_CONN);
+    if (i->second.hb_interval_start == utime_t())
+      i->second.hb_interval_start = now;
+
+    std::optional<ceph::signedspan> delta_ub;
+    s->stamps->sent_ping(&delta_ub);
+
+    i->second.con_back->send_message(
+      new MOSDPing(monc->get_fsid(),
+                  service.get_osdmap_epoch(),
+                  MOSDPing::PING,
+                  now,
+                  mnow,
+                  mnow,
+                  service.get_up_epoch(),
+                  cct->_conf->osd_heartbeat_min_size,
+                  delta_ub));
 
     if (i->second.con_front)
-      i->second.con_front->send_message(new MOSDPing(monc->get_fsid(),
-                                            service.get_osdmap()->get_epoch(),
-                                                    MOSDPing::PING,
-                                                    now));
+      i->second.con_front->send_message(
+       new MOSDPing(monc->get_fsid(),
+                    service.get_osdmap_epoch(),
+                    MOSDPing::PING,
+                    now,
+                    mnow,
+                    mnow,
+                    service.get_up_epoch(),
+                    cct->_conf->osd_heartbeat_min_size,
+                    delta_ub));
   }
 
   logger->set(l_osd_hb_to, heartbeat_peers.size());
@@ -4777,7 +5848,7 @@ void OSD::heartbeat()
     if (now - last_mon_heartbeat > cct->_conf->osd_mon_heartbeat_interval && is_active()) {
       last_mon_heartbeat = now;
       dout(10) << "i have no heartbeat peers; checking mon for new map" << dendl;
-      osdmap_subscribe(osdmap->get_epoch() + 1, false);
+      osdmap_subscribe(get_osdmap_epoch() + 1, false);
     }
   }
 
@@ -4786,36 +5857,31 @@ void OSD::heartbeat()
 
 bool OSD::heartbeat_reset(Connection *con)
 {
-  HeartbeatSession *s = static_cast<HeartbeatSession*>(con->get_priv());
+  std::lock_guard l(heartbeat_lock);
+  auto s = con->get_priv();
+  dout(20) << __func__ << " con " << con << " s " << s.get() << dendl;
+  con->set_priv(nullptr);
   if (s) {
-    heartbeat_lock.Lock();
     if (is_stopping()) {
-      heartbeat_lock.Unlock();
-      s->put();
       return true;
     }
-    map<int,HeartbeatInfo>::iterator p = heartbeat_peers.find(s->peer);
+    auto session = static_cast<Session*>(s.get());
+    auto p = heartbeat_peers.find(session->peer);
     if (p != heartbeat_peers.end() &&
        (p->second.con_back == con ||
         p->second.con_front == con)) {
       dout(10) << "heartbeat_reset failed hb con " << con << " for osd." << p->second.peer
               << ", reopening" << dendl;
-      if (con != p->second.con_back) {
-       p->second.con_back->mark_down();
-      }
-      p->second.con_back.reset(NULL);
-      if (p->second.con_front && con != p->second.con_front) {
-       p->second.con_front->mark_down();
-      }
-      p->second.con_front.reset(NULL);
+      p->second.clear_mark_down(con);
       pair<ConnectionRef,ConnectionRef> newcon = service.get_con_osd_hb(p->second.peer, p->second.epoch);
       if (newcon.first) {
        p->second.con_back = newcon.first.get();
-       p->second.con_back->set_priv(s->get());
+       p->second.con_back->set_priv(s);
        if (newcon.second) {
          p->second.con_front = newcon.second.get();
-         p->second.con_front->set_priv(s->get());
+         p->second.con_front->set_priv(s);
        }
+        p->second.ping_history.clear();
       } else {
        dout(10) << "heartbeat_reset failed hb con " << con << " for osd." << p->second.peer
                 << ", raced with osdmap update, closing out peer" << dendl;
@@ -4824,8 +5890,6 @@ bool OSD::heartbeat_reset(Connection *con)
     } else {
       dout(10) << "heartbeat_reset closing (old) failed hb con " << con << dendl;
     }
-    heartbeat_lock.Unlock();
-    s->put();
   }
   return true;
 }
@@ -4836,9 +5900,16 @@ bool OSD::heartbeat_reset(Connection *con)
 
 void OSD::tick()
 {
-  assert(osd_lock.is_locked());
+  ceph_assert(ceph_mutex_is_locked(osd_lock));
   dout(10) << "tick" << dendl;
 
+  utime_t now = ceph_clock_now();
+  // throw out any obsolete markdown log
+  utime_t grace = utime_t(cct->_conf->osd_max_markdown_period, 0);
+  while (!osd_markdown_log.empty() &&
+          osd_markdown_log.front() + grace < now)
+    osd_markdown_log.pop_front();
+
   if (is_active() || is_waiting_for_healthy()) {
     maybe_update_heartbeat_peers();
   }
@@ -4847,97 +5918,89 @@ void OSD::tick()
     start_boot();
   }
 
-  do_waiters();
+  if (is_waiting_for_healthy() || is_booting()) {
+    std::lock_guard l(heartbeat_lock);
+    if (now - last_mon_heartbeat > cct->_conf->osd_mon_heartbeat_interval) {
+      last_mon_heartbeat = now;
+      dout(1) << __func__ << " checking mon for new map" << dendl;
+      osdmap_subscribe(get_osdmap_epoch() + 1, false);
+    }
+  }
 
-  tick_timer.add_event_after(OSD_TICK_INTERVAL, new C_Tick(this));
+  do_waiters();
 
-  if (is_active()) {
-    const auto now = ceph::coarse_mono_clock::now();
-    const auto elapsed = now - last_sent_beacon;
-    if (chrono::duration_cast<chrono::seconds>(elapsed).count() >
-       cct->_conf->osd_beacon_report_interval) {
-      send_beacon(now);
+  // scrub purged_snaps every deep scrub interval
+  {
+    const utime_t last = superblock.last_purged_snaps_scrub;
+    utime_t next = last;
+    next += cct->_conf->osd_scrub_min_interval;
+    std::mt19937 rng;
+    // use a seed that is stable for each scrub interval, but varies
+    // by OSD to avoid any herds.
+    rng.seed(whoami + superblock.last_purged_snaps_scrub.sec());
+    double r = (rng() % 1024) / 1024;
+    next +=
+      cct->_conf->osd_scrub_min_interval *
+      cct->_conf->osd_scrub_interval_randomize_ratio * r;
+    if (next < ceph_clock_now()) {
+      dout(20) << __func__ << " last_purged_snaps_scrub " << last
+              << " next " << next << " ... now" << dendl;
+      scrub_purged_snaps();
+    } else {
+      dout(20) << __func__ << " last_purged_snaps_scrub " << last
+              << " next " << next << dendl;
     }
   }
+
+  tick_timer.add_event_after(get_tick_interval(), new C_Tick(this));
 }
 
 void OSD::tick_without_osd_lock()
 {
-  assert(tick_timer_lock.is_locked());
+  ceph_assert(ceph_mutex_is_locked(tick_timer_lock));
   dout(10) << "tick_without_osd_lock" << dendl;
 
-  logger->set(l_osd_buf, buffer::get_total_alloc());
-  logger->set(l_osd_history_alloc_bytes, SHIFT_ROUND_UP(buffer::get_history_alloc_bytes(), 20));
-  logger->set(l_osd_history_alloc_num, buffer::get_history_alloc_num());
-  logger->set(l_osd_cached_crc, buffer::get_cached_crc());
-  logger->set(l_osd_cached_crc_adjusted, buffer::get_cached_crc_adjusted());
-  logger->set(l_osd_missed_crc, buffer::get_missed_crc());
+  logger->set(l_osd_cached_crc, ceph::buffer::get_cached_crc());
+  logger->set(l_osd_cached_crc_adjusted, ceph::buffer::get_cached_crc_adjusted());
+  logger->set(l_osd_missed_crc, ceph::buffer::get_missed_crc());
+
+  // refresh osd stats
+  struct store_statfs_t stbuf;
+  osd_alert_list_t alerts;
+  int r = store->statfs(&stbuf, &alerts);
+  ceph_assert(r == 0);
+  service.set_statfs(stbuf, alerts);
 
   // osd_lock is not being held, which means the OSD state
   // might change when doing the monitor report
   if (is_active() || is_waiting_for_healthy()) {
-    heartbeat_lock.Lock();
-    heartbeat_check();
-    heartbeat_lock.Unlock();
-
-    map_lock.get_read();
-    Mutex::Locker l(mon_report_lock);
+    {
+      std::lock_guard l{heartbeat_lock};
+      heartbeat_check();
+    }
+    map_lock.lock_shared();
+    std::lock_guard l(mon_report_lock);
 
     // mon report?
-    bool reset = false;
-    bool report = false;
     utime_t now = ceph_clock_now();
-    pg_stat_queue_lock.Lock();
-    double backoff = stats_ack_timeout / cct->_conf->osd_mon_ack_timeout;
-    double adjusted_min = cct->_conf->osd_mon_report_interval_min * backoff;
-    // note: we shouldn't adjust max because it must remain < the
-    // mon's mon_osd_report_timeout (which defaults to 1.5x our
-    // value).
-    double max = cct->_conf->osd_mon_report_interval_max;
-    if (!outstanding_pg_stats.empty() &&
-       (now - stats_ack_timeout) > last_pg_stats_ack) {
-      dout(1) << __func__ << " mon hasn't acked PGStats in "
-             << now - last_pg_stats_ack
-             << " seconds, reconnecting elsewhere" << dendl;
-      reset = true;
-      last_pg_stats_ack = now;  // reset clock
-      last_pg_stats_sent = utime_t();
-      stats_ack_timeout =
-       MAX(cct->_conf->osd_mon_ack_timeout,
-           stats_ack_timeout * cct->_conf->osd_stats_ack_timeout_factor);
-      outstanding_pg_stats.clear();
-    }
-    if (now - last_pg_stats_sent > max) {
-      osd_stat_updated = true;
-      report = true;
-    } else if (service.need_fullness_update()) {
-      report = true;
-    } else if ((int)outstanding_pg_stats.size() >=
-              cct->_conf->osd_mon_report_max_in_flight) {
-      dout(20) << __func__ << " have max " << outstanding_pg_stats
-              << " stats updates in flight" << dendl;
-    } else {
-      if (now - last_mon_report > adjusted_min) {
-       dout(20) << __func__ << " stats backoff " << backoff
-                << " adjusted_min " << adjusted_min << " - sending report"
-                << dendl;
-        osd_stat_updated = true;
-       report = true;
-      }
-    }
-    pg_stat_queue_lock.Unlock();
-
-    if (reset) {
-      monc->reopen_session();
-    } else if (report) {
+    if (service.need_fullness_update() ||
+       now - last_mon_report > cct->_conf->osd_mon_report_interval) {
       last_mon_report = now;
-
-      // do any pending reports
       send_full_update();
       send_failures();
-      send_pg_stats(now);
     }
-    map_lock.put_read();
+    map_lock.unlock_shared();
+
+    epoch_t max_waiting_epoch = 0;
+    for (auto s : shards) {
+      max_waiting_epoch = std::max(max_waiting_epoch,
+                                  s->get_max_waiting_epoch());
+    }
+    if (max_waiting_epoch > get_osdmap()->get_epoch()) {
+      dout(20) << __func__ << " max_waiting_epoch " << max_waiting_epoch
+              << ", requesting new map" << dendl;
+      osdmap_subscribe(superblock.newest_map + 1, false);
+    }
   }
 
   if (is_active()) {
@@ -4945,23 +6008,27 @@ void OSD::tick_without_osd_lock()
       sched_scrub();
     }
     service.promote_throttle_recalibrate();
+    resume_creating_pg();
+    bool need_send_beacon = false;
+    const auto now = ceph::coarse_mono_clock::now();
+    {
+      // borrow lec lock to pretect last_sent_beacon from changing
+      std::lock_guard l{min_last_epoch_clean_lock};
+      const auto elapsed = now - last_sent_beacon;
+      if (std::chrono::duration_cast<std::chrono::seconds>(elapsed).count() >
+        cct->_conf->osd_beacon_report_interval) {
+        need_send_beacon = true;
+      }
+    }
+    if (need_send_beacon) {
+      send_beacon(now);
+    }
   }
 
-  check_ops_in_flight();
+  mgrc.update_daemon_health(get_health_metrics());
   service.kick_recovery_queue();
-  tick_timer_without_osd_lock.add_event_after(OSD_TICK_INTERVAL, new C_Tick_WithoutOSDLock(this));
-}
-
-void OSD::check_ops_in_flight()
-{
-  vector<string> warnings;
-  if (op_tracker.check_ops_in_flight(warnings)) {
-    for (vector<string>::iterator i = warnings.begin();
-        i != warnings.end();
-        ++i) {
-      clog->warn() << *i;
-    }
-  }
+  tick_timer_without_osd_lock.add_event_after(get_tick_interval(),
+                                             new C_Tick_WithoutOSDLock(this));
 }
 
 // Usage:
@@ -4975,7 +6042,8 @@ void OSD::check_ops_in_flight()
 //
 //   set_recovery_delay [utime]
 void TestOpsSocketHook::test_ops(OSDService *service, ObjectStore *store,
-     const std::string &command, cmdmap_t& cmdmap, ostream &ss)
+                                std::string_view command,
+                                const cmdmap_t& cmdmap, ostream &ss)
 {
   //Test support
   //Support changing the omap on a single osd by using the Admin Socket to
@@ -4992,18 +6060,18 @@ void TestOpsSocketHook::test_ops(OSDService *service, ObjectStore *store,
 
     string poolstr;
 
-    cmd_getval(service->cct, cmdmap, "pool", poolstr);
+    cmd_getval(cmdmap, "pool", poolstr);
     pool = curmap->lookup_pg_pool_name(poolstr);
     //If we can't find it by name then maybe id specified
     if (pool < 0 && isdigit(poolstr[0]))
       pool = atoll(poolstr.c_str());
     if (pool < 0) {
-      ss << "Invalid pool" << poolstr;
+      ss << "Invalid pool '" << poolstr << "''";
       return;
     }
 
     string objname, nspace;
-    cmd_getval(service->cct, cmdmap, "objname", objname);
+    cmd_getval(cmdmap, "objname", objname);
     std::size_t found = objname.find_first_of('/');
     if (found != string::npos) {
       nspace = objname.substr(0, found);
@@ -5018,7 +6086,7 @@ void TestOpsSocketHook::test_ops(OSDService *service, ObjectStore *store,
     }
 
     int64_t shardid;
-    cmd_getval(service->cct, cmdmap, "shardid", shardid, int64_t(shard_id_t::NO_SHARD));
+    cmd_getval(cmdmap, "shardid", shardid, int64_t(shard_id_t::NO_SHARD));
     hobject_t obj(object_t(objname), string(""), CEPH_NOSNAP, rawpg.ps(), pool, nspace);
     ghobject_t gobj(obj, ghobject_t::NO_GEN, shard_id_t(uint8_t(shardid)));
     spg_t pgid(curmap->raw_pg_to_pg(rawpg), shard_id_t(shardid));
@@ -5035,25 +6103,23 @@ void TestOpsSocketHook::test_ops(OSDService *service, ObjectStore *store,
       map<string, bufferlist> newattrs;
       bufferlist val;
       string key, valstr;
-      cmd_getval(service->cct, cmdmap, "key", key);
-      cmd_getval(service->cct, cmdmap, "val", valstr);
+      cmd_getval(cmdmap, "key", key);
+      cmd_getval(cmdmap, "val", valstr);
 
       val.append(valstr);
       newattrs[key] = val;
       t.omap_setkeys(coll_t(pgid), ghobject_t(obj), newattrs);
-      r = store->apply_transaction(service->meta_osr.get(), std::move(t));
+      r = store->queue_transaction(service->meta_ch, std::move(t));
       if (r < 0)
         ss << "error=" << r;
       else
         ss << "ok";
     } else if (command == "rmomapkey") {
       string key;
-      set<string> keys;
-      cmd_getval(service->cct, cmdmap, "key", key);
+      cmd_getval(cmdmap, "key", key);
 
-      keys.insert(key);
-      t.omap_rmkeys(coll_t(pgid), ghobject_t(obj), keys);
-      r = store->apply_transaction(service->meta_osr.get(), std::move(t));
+      t.omap_rmkey(coll_t(pgid), ghobject_t(obj), key);
+      r = store->queue_transaction(service->meta_ch, std::move(t));
       if (r < 0)
         ss << "error=" << r;
       else
@@ -5062,10 +6128,10 @@ void TestOpsSocketHook::test_ops(OSDService *service, ObjectStore *store,
       bufferlist newheader;
       string headerstr;
 
-      cmd_getval(service->cct, cmdmap, "header", headerstr);
+      cmd_getval(cmdmap, "header", headerstr);
       newheader.append(headerstr);
       t.omap_setheader(coll_t(pgid), ghobject_t(obj), newheader);
-      r = store->apply_transaction(service->meta_osr.get(), std::move(t));
+      r = store->queue_transaction(service->meta_ch, std::move(t));
       if (r < 0)
         ss << "error=" << r;
       else
@@ -5074,21 +6140,27 @@ void TestOpsSocketHook::test_ops(OSDService *service, ObjectStore *store,
       //Debug: Output entire omap
       bufferlist hdrbl;
       map<string, bufferlist> keyvals;
-      r = store->omap_get(coll_t(pgid), ghobject_t(obj), &hdrbl, &keyvals);
-      if (r >= 0) {
+      auto ch = store->open_collection(coll_t(pgid));
+      if (!ch) {
+       ss << "unable to open collection for " << pgid;
+       r = -ENOENT;
+      } else {
+       r = store->omap_get(ch, ghobject_t(obj), &hdrbl, &keyvals);
+       if (r >= 0) {
           ss << "header=" << string(hdrbl.c_str(), hdrbl.length());
           for (map<string, bufferlist>::iterator it = keyvals.begin();
-              it != keyvals.end(); ++it)
+              it != keyvals.end(); ++it)
             ss << " key=" << (*it).first << " val="
                << string((*it).second.c_str(), (*it).second.length());
-      } else {
+       } else {
           ss << "error=" << r;
+       }
       }
     } else if (command == "truncobj") {
       int64_t trunclen;
-      cmd_getval(service->cct, cmdmap, "len", trunclen);
+      cmd_getval(cmdmap, "len", trunclen);
       t.truncate(coll_t(pgid), ghobject_t(obj), trunclen);
-      r = store->apply_transaction(service->meta_osr.get(), std::move(t));
+      r = store->queue_transaction(service->meta_ch, std::move(t));
       if (r < 0)
        ss << "error=" << r;
       else
@@ -5104,10 +6176,10 @@ void TestOpsSocketHook::test_ops(OSDService *service, ObjectStore *store,
   }
   if (command == "set_recovery_delay") {
     int64_t delay;
-    cmd_getval(service->cct, cmdmap, "utime", delay, (int64_t)0);
+    cmd_getval(cmdmap, "utime", delay, (int64_t)0);
     ostringstream oss;
     oss << delay;
-    int r = service->cct->_conf->set_val("osd_recovery_delay_start",
+    int r = service->cct->_conf.set_val("osd_recovery_delay_start",
                                         oss.str().c_str());
     if (r != 0) {
       ss << "set_recovery_delay: error setting "
@@ -5115,55 +6187,17 @@ void TestOpsSocketHook::test_ops(OSDService *service, ObjectStore *store,
         << r;
       return;
     }
-    service->cct->_conf->apply_changes(NULL);
+    service->cct->_conf.apply_changes(nullptr);
     ss << "set_recovery_delay: set osd_recovery_delay_start "
        << "to " << service->cct->_conf->osd_recovery_delay_start;
     return;
   }
-  if (command ==  "trigger_scrub") {
-    spg_t pgid;
-    OSDMapRef curmap = service->get_osdmap();
-
-    string pgidstr;
-
-    cmd_getval(service->cct, cmdmap, "pgid", pgidstr);
-    if (!pgid.parse(pgidstr.c_str())) {
-      ss << "Invalid pgid specified";
-      return;
-    }
-
-    PG *pg = service->osd->_lookup_lock_pg(pgid);
-    if (pg == nullptr) {
-      ss << "Can't find pg " << pgid;
-      return;
-    }
-
-    if (pg->is_primary()) {
-      pg->unreg_next_scrub();
-      const pg_pool_t *p = curmap->get_pg_pool(pgid.pool());
-      double pool_scrub_max_interval = 0;
-      p->opts.get(pool_opts_t::SCRUB_MAX_INTERVAL, &pool_scrub_max_interval);
-      double scrub_max_interval = pool_scrub_max_interval > 0 ?
-        pool_scrub_max_interval : g_conf->osd_scrub_max_interval;
-      // Instead of marking must_scrub force a schedule scrub
-      utime_t stamp = ceph_clock_now();
-      stamp -= scrub_max_interval;
-      stamp -=  100.0;  // push back last scrub more for good measure
-      pg->info.history.last_scrub_stamp = stamp;
-      pg->reg_next_scrub();
-      ss << "ok";
-    } else {
-      ss << "Not primary";
-    }
-    pg->unlock();
-    return;
-  }
   if (command == "injectfull") {
     int64_t count;
     string type;
     OSDService::s_names state;
-    cmd_getval(service->cct, cmdmap, "type", type, string("full"));
-    cmd_getval(service->cct, cmdmap, "count", count, (int64_t)-1);
+    cmd_getval(cmdmap, "type", type, string("full"));
+    cmd_getval(cmdmap, "count", count, (int64_t)-1);
     if (type == "none" || count == 0) {
       type = "none";
       count = 0;
@@ -5179,130 +6213,13 @@ void TestOpsSocketHook::test_ops(OSDService *service, ObjectStore *store,
   ss << "Internal error - command=" << command;
 }
 
-// =========================================
-bool remove_dir(
-  CephContext *cct,
-  ObjectStore *store, SnapMapper *mapper,
-  OSDriver *osdriver,
-  ObjectStore::Sequencer *osr,
-  coll_t coll, DeletingStateRef dstate,
-  bool *finished,
-  ThreadPool::TPHandle &handle)
-{
-  vector<ghobject_t> olist;
-  int64_t num = 0;
-  ObjectStore::Transaction t;
-  ghobject_t next;
-  handle.reset_tp_timeout();
-  store->collection_list(
-    coll,
-    next,
-    ghobject_t::get_max(),
-    store->get_ideal_list_max(),
-    &olist,
-    &next);
-  generic_dout(10) << __func__ << " " << olist << dendl;
-  // default cont to true, this is safe because caller(OSD::RemoveWQ::_process()) 
-  // will recheck the answer before it really goes on.
-  bool cont = true;
-  for (vector<ghobject_t>::iterator i = olist.begin();
-       i != olist.end();
-       ++i) {
-    if (i->is_pgmeta())
-      continue;
-    OSDriver::OSTransaction _t(osdriver->get_transaction(&t));
-    int r = mapper->remove_oid(i->hobj, &_t);
-    if (r != 0 && r != -ENOENT) {
-      ceph_abort();
-    }
-    t.remove(coll, *i);
-    if (++num >= cct->_conf->osd_target_transaction_size) {
-      C_SaferCond waiter;
-      store->queue_transaction(osr, std::move(t), &waiter);
-      cont = dstate->pause_clearing();
-      handle.suspend_tp_timeout();
-      waiter.wait();
-      handle.reset_tp_timeout();
-      if (cont)
-        cont = dstate->resume_clearing();
-      if (!cont)
-       return false;
-      t = ObjectStore::Transaction();
-      num = 0;
-    }
-  }
-  if (num) {
-    C_SaferCond waiter;
-    store->queue_transaction(osr, std::move(t), &waiter);
-    cont = dstate->pause_clearing();
-    handle.suspend_tp_timeout();
-    waiter.wait();
-    handle.reset_tp_timeout();
-    if (cont)
-      cont = dstate->resume_clearing();
-  }
-  // whether there are more objects to remove in the collection
-  *finished = next.is_max();
-  return cont;
-}
-
-void OSD::RemoveWQ::_process(
-  pair<PGRef, DeletingStateRef> item,
-  ThreadPool::TPHandle &handle)
-{
-  FUNCTRACE();
-  PGRef pg(item.first);
-  SnapMapper &mapper = pg->snap_mapper;
-  OSDriver &driver = pg->osdriver;
-  coll_t coll = coll_t(pg->info.pgid);
-  pg->osr->flush();
-  bool finished = false;
-
-  if (!item.second->start_or_resume_clearing())
-    return;
-
-  bool cont = remove_dir(
-    pg->cct, store, &mapper, &driver, pg->osr.get(), coll, item.second,
-    &finished, handle);
-  if (!cont)
-    return;
-  if (!finished) {
-    if (item.second->pause_clearing())
-      queue_front(item);
-    return;
-  }
-
-  if (!item.second->start_deleting())
-    return;
-
-  ObjectStore::Transaction t;
-  PGLog::clear_info_log(pg->info.pgid, &t);
-
-  if (cct->_conf->osd_inject_failure_on_pg_removal) {
-    generic_derr << "osd_inject_failure_on_pg_removal" << dendl;
-    _exit(1);
-  }
-  t.remove_collection(coll);
-
-  // We need the sequencer to stick around until the op is complete
-  store->queue_transaction(
-    pg->osr.get(),
-    std::move(t),
-    0, // onapplied
-    0, // oncommit
-    0, // onreadable sync
-    new ContainerContext<PGRef>(pg),
-    TrackedOpRef());
-
-  item.second->finish_deleting();
-}
 // =========================================
 
 void OSD::ms_handle_connect(Connection *con)
 {
   dout(10) << __func__ << " con " << con << dendl;
   if (con->get_peer_type() == CEPH_ENTITY_TYPE_MON) {
-    Mutex::Locker l(osd_lock);
+    std::lock_guard l(osd_lock);
     if (is_stopping())
       return;
     dout(10) << __func__ << " on mon" << dendl;
@@ -5312,8 +6229,8 @@ void OSD::ms_handle_connect(Connection *con)
     } else if (is_booting()) {
       _send_boot();       // resend boot message
     } else {
-      map_lock.get_read();
-      Mutex::Locker l2(mon_report_lock);
+      map_lock.lock_shared();
+      std::lock_guard l2(mon_report_lock);
 
       utime_t now = ceph_clock_now();
       last_mon_report = now;
@@ -5322,12 +6239,14 @@ void OSD::ms_handle_connect(Connection *con)
       send_full_update();
       send_alive();
       service.requeue_pg_temp();
+      service.clear_sent_ready_to_merge();
       service.send_pg_temp();
+      service.send_ready_to_merge();
+      service.send_pg_created();
       requeue_failures();
       send_failures();
-      send_pg_stats(now);
 
-      map_lock.put_read();
+      map_lock.unlock_shared();
       if (is_active()) {
        send_beacon(ceph::coarse_mono_clock::now());
       }
@@ -5344,18 +6263,15 @@ void OSD::ms_handle_fast_connect(Connection *con)
 {
   if (con->get_peer_type() != CEPH_ENTITY_TYPE_MON &&
       con->get_peer_type() != CEPH_ENTITY_TYPE_MGR) {
-    Session *s = static_cast<Session*>(con->get_priv());
-    if (!s) {
-      s = new Session(cct);
-      con->set_priv(s->get());
-      s->con = con;
+    if (auto s = ceph::ref_cast<Session>(con->get_priv()); !s) {
+      s = ceph::make_ref<Session>(cct, con);
+      con->set_priv(s);
       dout(10) << " new session (outgoing) " << s << " con=" << s->con
           << " addr=" << s->con->get_peer_addr() << dendl;
       // we don't connect to clients
-      assert(con->get_peer_type() == CEPH_ENTITY_TYPE_OSD);
+      ceph_assert(con->get_peer_type() == CEPH_ENTITY_TYPE_OSD);
       s->entity_name.set_type(CEPH_ENTITY_TYPE_OSD);
     }
-    s->put();
   }
 }
 
@@ -5363,34 +6279,31 @@ void OSD::ms_handle_fast_accept(Connection *con)
 {
   if (con->get_peer_type() != CEPH_ENTITY_TYPE_MON &&
       con->get_peer_type() != CEPH_ENTITY_TYPE_MGR) {
-    Session *s = static_cast<Session*>(con->get_priv());
-    if (!s) {
-      s = new Session(cct);
-      con->set_priv(s->get());
-      s->con = con;
+    if (auto s = ceph::ref_cast<Session>(con->get_priv()); !s) {
+      s = ceph::make_ref<Session>(cct, con);
+      con->set_priv(s);
       dout(10) << "new session (incoming)" << s << " con=" << con
           << " addr=" << con->get_peer_addr()
           << " must have raced with connect" << dendl;
-      assert(con->get_peer_type() == CEPH_ENTITY_TYPE_OSD);
+      ceph_assert(con->get_peer_type() == CEPH_ENTITY_TYPE_OSD);
       s->entity_name.set_type(CEPH_ENTITY_TYPE_OSD);
     }
-    s->put();
   }
 }
 
 bool OSD::ms_handle_reset(Connection *con)
 {
-  Session *session = static_cast<Session*>(con->get_priv());
-  dout(2) << "ms_handle_reset con " << con << " session " << session << dendl;
+  auto session = ceph::ref_cast<Session>(con->get_priv());
+  dout(2) << "ms_handle_reset con " << con << " session " << session.get() << dendl;
   if (!session)
     return false;
   session->wstate.reset(con);
-  session->con.reset(NULL);  // break con <-> session ref cycle
+  session->con->set_priv(nullptr);
+  session->con.reset();  // break con <-> session ref cycle
   // note that we break session->con *before* the session_handle_reset
   // cleanup below.  this avoids a race between us and
   // PG::add_backoff, Session::check_backoff, etc.
   session_handle_reset(session);
-  session->put();
   return true;
 }
 
@@ -5399,8 +6312,8 @@ bool OSD::ms_handle_refused(Connection *con)
   if (!cct->_conf->osd_fast_fail_on_connection_refused)
     return false;
 
-  Session *session = static_cast<Session*>(con->get_priv());
-  dout(2) << "ms_handle_refused con " << con << " session " << session << dendl;
+  auto session = ceph::ref_cast<Session>(con->get_priv());
+  dout(2) << "ms_handle_refused con " << con << " session " << session.get() << dendl;
   if (!session)
     return false;
   int type = con->get_peer_type();
@@ -5412,25 +6325,27 @@ bool OSD::ms_handle_refused(Connection *con)
       if (id >= 0 && osdmap->is_up(id)) {
        // I'm cheating mon heartbeat grace logic, because we know it's not going
        // to respawn alone. +1 so we won't hit any boundary case.
-       monc->send_mon_message(new MOSDFailure(monc->get_fsid(),
-                                                 osdmap->get_inst(id),
-                                                 cct->_conf->osd_heartbeat_grace + 1,
-                                                 osdmap->get_epoch(),
-                                                 MOSDFailure::FLAG_IMMEDIATE | MOSDFailure::FLAG_FAILED
-                                                 ));
+       monc->send_mon_message(
+         new MOSDFailure(
+           monc->get_fsid(),
+           id,
+           osdmap->get_addrs(id),
+           cct->_conf->osd_heartbeat_grace + 1,
+           osdmap->get_epoch(),
+           MOSDFailure::FLAG_IMMEDIATE | MOSDFailure::FLAG_FAILED
+           ));
       }
     }
   }
-  session->put();
   return true;
 }
 
-struct C_OSD_GetVersion : public Context {
+struct CB_OSD_GetVersion {
   OSD *osd;
-  uint64_t oldest, newest;
-  explicit C_OSD_GetVersion(OSD *o) : osd(o), oldest(0), newest(0) {}
-  void finish(int r) override {
-    if (r >= 0)
+  explicit CB_OSD_GetVersion(OSD *o) : osd(o) {}
+  void operator ()(boost::system::error_code ec, version_t newest,
+                  version_t oldest) {
+    if (!ec)
       osd->_got_mon_epochs(oldest, newest);
   }
 };
@@ -5450,13 +6365,12 @@ void OSD::start_boot()
   set_state(STATE_PREBOOT);
   dout(10) << "start_boot - have maps " << superblock.oldest_map
           << ".." << superblock.newest_map << dendl;
-  C_OSD_GetVersion *c = new C_OSD_GetVersion(this);
-  monc->get_version("osdmap", &c->newest, &c->oldest, c);
+  monc->get_version("osdmap", CB_OSD_GetVersion(this));
 }
 
 void OSD::_got_mon_epochs(epoch_t oldest, epoch_t newest)
 {
-  Mutex::Locker l(osd_lock);
+  std::lock_guard l(osd_lock);
   if (is_preboot()) {
     _preboot(oldest, newest);
   }
@@ -5464,32 +6378,64 @@ void OSD::_got_mon_epochs(epoch_t oldest, epoch_t newest)
 
 void OSD::_preboot(epoch_t oldest, epoch_t newest)
 {
-  assert(is_preboot());
+  ceph_assert(is_preboot());
   dout(10) << __func__ << " _preboot mon has osdmaps "
           << oldest << ".." << newest << dendl;
 
   // ensure our local fullness awareness is accurate
-  heartbeat();
+  {
+    std::lock_guard l(heartbeat_lock);
+    heartbeat();
+  }
 
+  const auto& monmap = monc->monmap;
+  const auto osdmap = get_osdmap();
   // if our map within recent history, try to add ourselves to the osdmap.
-  if (osdmap->test_flag(CEPH_OSDMAP_NOUP)) {
+  if (osdmap->get_epoch() == 0) {
+    derr << "waiting for initial osdmap" << dendl;
+  } else if (osdmap->is_destroyed(whoami)) {
+    derr << "osdmap says I am destroyed" << dendl;
+    // provide a small margin so we don't livelock seeing if we
+    // un-destroyed ourselves.
+    if (osdmap->get_epoch() > newest - 1) {
+      exit(0);
+    }
+  } else if (osdmap->is_noup(whoami)) {
     derr << "osdmap NOUP flag is set, waiting for it to clear" << dendl;
   } else if (!osdmap->test_flag(CEPH_OSDMAP_SORTBITWISE)) {
     derr << "osdmap SORTBITWISE OSDMap flag is NOT set; please set it"
         << dendl;
-  } else if (!osdmap->test_flag(CEPH_OSDMAP_REQUIRE_JEWEL)) {
-    derr << "osdmap REQUIRE_JEWEL OSDMap flag is NOT set; please set it"
-        << dendl;
-  } else if (!monc->monmap.get_required_features().contains_all(
-              ceph::features::mon::FEATURE_LUMINOUS)) {
-    derr << "monmap REQUIRE_LUMINOUS is NOT set; must upgrade all monitors to "
-        << "Luminous or later before Luminous OSDs will boot" << dendl;
   } else if (service.need_fullness_update()) {
     derr << "osdmap fullness state needs update" << dendl;
     send_full_update();
+  } else if (monmap.min_mon_release >= ceph_release_t::octopus &&
+            superblock.purged_snaps_last < superblock.current_epoch) {
+    dout(10) << __func__ << " purged_snaps_last " << superblock.purged_snaps_last
+            << " < newest_map " << superblock.current_epoch << dendl;
+    _get_purged_snaps();
   } else if (osdmap->get_epoch() >= oldest - 1 &&
             osdmap->get_epoch() + cct->_conf->osd_map_message_max > newest) {
-    _send_boot();
+
+    // wait for pgs to fully catch up in a different thread, since
+    // this thread might be required for splitting and merging PGs to
+    // make progress.
+    boot_finisher.queue(
+      new LambdaContext(
+       [this](int r) {
+         std::unique_lock l(osd_lock);
+         if (is_preboot()) {
+           dout(10) << __func__ << " waiting for peering work to drain"
+                    << dendl;
+           l.unlock();
+           for (auto shard : shards) {
+             shard->wait_min_pg_epoch(get_osdmap_epoch());
+           }
+           l.lock();
+         }
+         if (is_preboot()) {
+           _send_boot();
+         }
+       }));
     return;
   }
 
@@ -5500,6 +6446,45 @@ void OSD::_preboot(epoch_t oldest, epoch_t newest)
     osdmap_subscribe(oldest - 1, true);
 }
 
+void OSD::_get_purged_snaps()
+{
+  // NOTE: this is a naive, stateless implementaiton.  it may send multiple
+  // overlapping requests to the mon, which will be somewhat inefficient, but
+  // it should be reliable.
+  dout(10) << __func__ << " purged_snaps_last " << superblock.purged_snaps_last
+          << ", newest_map " << superblock.current_epoch << dendl;
+  MMonGetPurgedSnaps *m = new MMonGetPurgedSnaps(
+    superblock.purged_snaps_last + 1,
+    superblock.current_epoch + 1);
+  monc->send_mon_message(m);
+}
+
+void OSD::handle_get_purged_snaps_reply(MMonGetPurgedSnapsReply *m)
+{
+  dout(10) << __func__ << " " << *m << dendl;
+  ObjectStore::Transaction t;
+  if (!is_preboot() ||
+      m->last < superblock.purged_snaps_last) {
+    goto out;
+  }
+  SnapMapper::record_purged_snaps(cct, store, service.meta_ch,
+                                 make_purged_snaps_oid(), &t,
+                                 m->purged_snaps);
+  superblock.purged_snaps_last = m->last;
+  write_superblock(t);
+  store->queue_transaction(
+    service.meta_ch,
+    std::move(t));
+  service.publish_superblock(superblock);
+  if (m->last < superblock.current_epoch) {
+    _get_purged_snaps();
+  } else {
+    start_boot();
+  }
+out:
+  m->put();
+}
+
 void OSD::send_full_update()
 {
   if (!service.need_fullness_update())
@@ -5515,7 +6500,7 @@ void OSD::send_full_update()
   set<string> s;
   OSDMap::calc_state_set(state, s);
   dout(10) << __func__ << " want state " << s << dendl;
-  monc->send_mon_message(new MOSDFull(osdmap->get_epoch(), state));
+  monc->send_mon_message(new MOSDFull(get_osdmap_epoch(), state));
 }
 
 void OSD::start_waiting_for_healthy()
@@ -5523,6 +6508,9 @@ void OSD::start_waiting_for_healthy()
   dout(1) << "start_waiting_for_healthy" << dendl;
   set_state(STATE_WAITING_FOR_HEALTHY);
   last_heartbeat_resample = utime_t();
+
+  // subscribe to osdmap updates, in case our peers really are known to be dead
+  osdmap_subscribe(get_osdmap_epoch() + 1, false);
 }
 
 bool OSD::_is_healthy()
@@ -5533,14 +6521,19 @@ bool OSD::_is_healthy()
   }
 
   if (is_waiting_for_healthy()) {
-    Mutex::Locker l(heartbeat_lock);
-    utime_t cutoff = ceph_clock_now();
-    cutoff -= cct->_conf->osd_heartbeat_grace;
+     utime_t now = ceph_clock_now();
+     if (osd_markdown_log.empty()) {
+       dout(5) << __func__ << " force returning true since last markdown"
+               << " was " << cct->_conf->osd_max_markdown_period
+               << "s ago" << dendl;
+       return true;
+    }
+    std::lock_guard l(heartbeat_lock);
     int num = 0, up = 0;
     for (map<int,HeartbeatInfo>::iterator p = heartbeat_peers.begin();
         p != heartbeat_peers.end();
         ++p) {
-      if (p->second.is_healthy(cutoff))
+      if (p->second.is_healthy(now))
        ++up;
       ++num;
     }
@@ -5557,61 +6550,60 @@ bool OSD::_is_healthy()
 void OSD::_send_boot()
 {
   dout(10) << "_send_boot" << dendl;
-  entity_addr_t cluster_addr = cluster_messenger->get_myaddr();
-  Connection *local_connection = cluster_messenger->get_loopback_connection().get();
-  if (cluster_addr.is_blank_ip()) {
-    int port = cluster_addr.get_port();
-    cluster_addr = client_messenger->get_myaddr();
-    cluster_addr.set_port(port);
-    cluster_messenger->set_addr_unknowns(cluster_addr);
-    dout(10) << " assuming cluster_addr ip matches client_addr" << dendl;
-  } else {
-    Session *s = static_cast<Session*>(local_connection->get_priv());
-    if (s)
-      s->put();
-    else
-      cluster_messenger->ms_deliver_handle_fast_connect(local_connection);
+  Connection *local_connection =
+    cluster_messenger->get_loopback_connection().get();
+  entity_addrvec_t client_addrs = client_messenger->get_myaddrs();
+  entity_addrvec_t cluster_addrs = cluster_messenger->get_myaddrs();
+  entity_addrvec_t hb_back_addrs = hb_back_server_messenger->get_myaddrs();
+  entity_addrvec_t hb_front_addrs = hb_front_server_messenger->get_myaddrs();
+
+  dout(20) << " initial client_addrs " << client_addrs
+          << ", cluster_addrs " << cluster_addrs
+          << ", hb_back_addrs " << hb_back_addrs
+          << ", hb_front_addrs " << hb_front_addrs
+          << dendl;
+  if (cluster_messenger->set_addr_unknowns(client_addrs)) {
+    dout(10) << " assuming cluster_addrs match client_addrs "
+            << client_addrs << dendl;
+    cluster_addrs = cluster_messenger->get_myaddrs();
+  }
+  if (auto session = local_connection->get_priv(); !session) {
+    cluster_messenger->ms_deliver_handle_fast_connect(local_connection);
   }
 
-  entity_addr_t hb_back_addr = hb_back_server_messenger->get_myaddr();
   local_connection = hb_back_server_messenger->get_loopback_connection().get();
-  if (hb_back_addr.is_blank_ip()) {
-    int port = hb_back_addr.get_port();
-    hb_back_addr = cluster_addr;
-    hb_back_addr.set_port(port);
-    hb_back_server_messenger->set_addr_unknowns(hb_back_addr);
-    dout(10) << " assuming hb_back_addr ip matches cluster_addr" << dendl;
-  } else {
-    Session *s = static_cast<Session*>(local_connection->get_priv());
-    if (s)
-      s->put();
-    else
-      hb_back_server_messenger->ms_deliver_handle_fast_connect(local_connection);
+  if (hb_back_server_messenger->set_addr_unknowns(cluster_addrs)) {
+    dout(10) << " assuming hb_back_addrs match cluster_addrs "
+            << cluster_addrs << dendl;
+    hb_back_addrs = hb_back_server_messenger->get_myaddrs();
   }
-
-  entity_addr_t hb_front_addr = hb_front_server_messenger->get_myaddr();
-  local_connection = hb_front_server_messenger->get_loopback_connection().get();
-  if (hb_front_addr.is_blank_ip()) {
-    int port = hb_front_addr.get_port();
-    hb_front_addr = client_messenger->get_myaddr();
-    hb_front_addr.set_port(port);
-    hb_front_server_messenger->set_addr_unknowns(hb_front_addr);
-    dout(10) << " assuming hb_front_addr ip matches client_addr" << dendl;
-  } else {
-    Session *s = static_cast<Session*>(local_connection->get_priv());
-    if (s)
-      s->put();
-    else
-      hb_front_server_messenger->ms_deliver_handle_fast_connect(local_connection);
+  if (auto session = local_connection->get_priv(); !session) {
+    hb_back_server_messenger->ms_deliver_handle_fast_connect(local_connection);
   }
 
-  MOSDBoot *mboot = new MOSDBoot(superblock, get_osdmap_epoch(), service.get_boot_epoch(),
-                                 hb_back_addr, hb_front_addr, cluster_addr,
-                                CEPH_FEATURES_ALL);
-  dout(10) << " client_addr " << client_messenger->get_myaddr()
-          << ", cluster_addr " << cluster_addr
-          << ", hb_back_addr " << hb_back_addr
-          << ", hb_front_addr " << hb_front_addr
+  local_connection = hb_front_server_messenger->get_loopback_connection().get();
+  if (hb_front_server_messenger->set_addr_unknowns(client_addrs)) {
+    dout(10) << " assuming hb_front_addrs match client_addrs "
+            << client_addrs << dendl;
+    hb_front_addrs = hb_front_server_messenger->get_myaddrs();
+  }
+  if (auto session = local_connection->get_priv(); !session) {
+    hb_front_server_messenger->ms_deliver_handle_fast_connect(local_connection);
+  }
+
+  // we now know what our front and back addrs will be, and we are
+  // about to tell the mon what our metadata (including numa bindings)
+  // are, so now is a good time!
+  set_numa_affinity();
+
+  MOSDBoot *mboot = new MOSDBoot(
+    superblock, get_osdmap_epoch(), service.get_boot_epoch(),
+    hb_back_addrs, hb_front_addrs, cluster_addrs,
+    CEPH_FEATURES_ALL);
+  dout(10) << " final client_addrs " << client_addrs
+          << ", cluster_addrs " << cluster_addrs
+          << ", hb_back_addrs " << hb_back_addrs
+          << ", hb_front_addrs " << hb_front_addrs
           << dendl;
   _collect_metadata(&mboot->metadata);
   monc->send_mon_message(mboot);
@@ -5622,26 +6614,90 @@ void OSD::_collect_metadata(map<string,string> *pm)
 {
   // config info
   (*pm)["osd_data"] = dev_path;
-  (*pm)["osd_journal"] = journal_path;
-  (*pm)["front_addr"] = stringify(client_messenger->get_myaddr());
-  (*pm)["back_addr"] = stringify(cluster_messenger->get_myaddr());
-  (*pm)["hb_front_addr"] = stringify(hb_front_server_messenger->get_myaddr());
-  (*pm)["hb_back_addr"] = stringify(hb_back_server_messenger->get_myaddr());
+  if (store->get_type() == "filestore") {
+    // not applicable for bluestore
+    (*pm)["osd_journal"] = journal_path;
+  }
+  (*pm)["front_addr"] = stringify(client_messenger->get_myaddrs());
+  (*pm)["back_addr"] = stringify(cluster_messenger->get_myaddrs());
+  (*pm)["hb_front_addr"] = stringify(hb_front_server_messenger->get_myaddrs());
+  (*pm)["hb_back_addr"] = stringify(hb_back_server_messenger->get_myaddrs());
 
   // backend
   (*pm)["osd_objectstore"] = store->get_type();
+  (*pm)["rotational"] = store_is_rotational ? "1" : "0";
+  (*pm)["journal_rotational"] = journal_is_rotational ? "1" : "0";
+  (*pm)["default_device_class"] = store->get_default_device_class();
+  string osdspec_affinity;
+  int r = store->read_meta("osdspec_affinity", &osdspec_affinity);
+  if (r < 0 || osdspec_affinity.empty()) {
+    osdspec_affinity = "";
+  }
+  (*pm)["osdspec_affinity"] = osdspec_affinity;
   store->collect_metadata(pm);
 
   collect_sys_info(pm, cct);
 
+  (*pm)["front_iface"] = pick_iface(
+    cct,
+    client_messenger->get_myaddrs().front().get_sockaddr_storage());
+  (*pm)["back_iface"] = pick_iface(
+    cct,
+    cluster_messenger->get_myaddrs().front().get_sockaddr_storage());
+
+  // network numa
+  {
+    int node = -1;
+    set<int> nodes;
+    set<string> unknown;
+    for (auto nm : { "front_iface", "back_iface" }) {
+      if (!(*pm)[nm].size()) {
+       unknown.insert(nm);
+       continue;
+      }
+      int n = -1;
+      int r = get_iface_numa_node((*pm)[nm], &n);
+      if (r < 0) {
+       unknown.insert((*pm)[nm]);
+       continue;
+      }
+      nodes.insert(n);
+      if (node < 0) {
+       node = n;
+      }
+    }
+    if (unknown.size()) {
+      (*pm)["network_numa_unknown_ifaces"] = stringify(unknown);
+    }
+    if (!nodes.empty()) {
+      (*pm)["network_numa_nodes"] = stringify(nodes);
+    }
+    if (node >= 0 && nodes.size() == 1 && unknown.empty()) {
+      (*pm)["network_numa_node"] = stringify(node);
+    }
+  }
+
+  if (numa_node >= 0) {
+    (*pm)["numa_node"] = stringify(numa_node);
+    (*pm)["numa_node_cpus"] = cpu_set_to_str_list(numa_cpu_set_size,
+                                                 &numa_cpu_set);
+  }
+
+  set<string> devnames;
+  store->get_devices(&devnames);
+  map<string,string> errs;
+  get_device_metadata(devnames, pm, &errs);
+  for (auto& i : errs) {
+    dout(1) << __func__ << " " << i.first << ": " << i.second << dendl;
+  }
   dout(10) << __func__ << " " << *pm << dendl;
 }
 
 void OSD::queue_want_up_thru(epoch_t want)
 {
-  map_lock.get_read();
-  epoch_t cur = osdmap->get_up_thru(whoami);
-  Mutex::Locker l(mon_report_lock);
+  std::shared_lock map_locker{map_lock};
+  epoch_t cur = get_osdmap()->get_up_thru(whoami);
+  std::lock_guard report_locker(mon_report_lock);
   if (want > up_thru_wanted) {
     dout(10) << "queue_want_up_thru now " << want << " (was " << up_thru_wanted << ")"
             << ", currently " << cur
@@ -5653,12 +6709,12 @@ void OSD::queue_want_up_thru(epoch_t want)
             << ", currently " << cur
             << dendl;
   }
-  map_lock.put_read();
 }
 
 void OSD::send_alive()
 {
-  assert(mon_report_lock.is_locked());
+  ceph_assert(ceph_mutex_is_locked(mon_report_lock));
+  const auto osdmap = get_osdmap();
   if (!osdmap->exists(whoami))
     return;
   epoch_t up_thru = osdmap->get_up_thru(whoami);
@@ -5674,10 +6730,10 @@ void OSD::request_full_map(epoch_t first, epoch_t last)
   dout(10) << __func__ << " " << first << ".." << last
           << ", previously requested "
           << requested_full_first << ".." << requested_full_last << dendl;
-  assert(osd_lock.is_locked());
-  assert(first > 0 && last > 0);
-  assert(first <= last);
-  assert(first >= requested_full_first);  // we shouldn't ever ask for older maps
+  ceph_assert(ceph_mutex_is_locked(osd_lock));
+  ceph_assert(first > 0 && last > 0);
+  ceph_assert(first <= last);
+  ceph_assert(first >= requested_full_first);  // we shouldn't ever ask for older maps
   if (requested_full_first == 0) {
     // first request
     requested_full_first = first;
@@ -5697,8 +6753,8 @@ void OSD::request_full_map(epoch_t first, epoch_t last)
 
 void OSD::got_full_map(epoch_t e)
 {
-  assert(requested_full_first <= requested_full_last);
-  assert(osd_lock.is_locked());
+  ceph_assert(requested_full_first <= requested_full_last);
+  ceph_assert(ceph_mutex_is_locked(osd_lock));
   if (requested_full_first == 0) {
     dout(20) << __func__ << " " << e << ", nothing requested" << dendl;
     return;
@@ -5715,7 +6771,7 @@ void OSD::got_full_map(epoch_t e)
     requested_full_first = requested_full_last = 0;
     return;
   }
-  
+
   requested_full_first = e + 1;
 
   dout(10) << __func__ << " " << e << ", requested " << requested_full_first
@@ -5725,12 +6781,10 @@ void OSD::got_full_map(epoch_t e)
 
 void OSD::requeue_failures()
 {
-  Mutex::Locker l(heartbeat_lock);
+  std::lock_guard l(heartbeat_lock);
   unsigned old_queue = failure_queue.size();
   unsigned old_pending = failure_pending.size();
-  for (map<int,pair<utime_t,entity_inst_t> >::iterator p =
-        failure_pending.begin();
-       p != failure_pending.end(); ) {
+  for (auto p = failure_pending.begin(); p != failure_pending.end(); ) {
     failure_queue[p->first] = p->second.first;
     failure_pending.erase(p++);
   }
@@ -5740,172 +6794,46 @@ void OSD::requeue_failures()
 
 void OSD::send_failures()
 {
-  assert(map_lock.is_locked());
-  assert(mon_report_lock.is_locked());
-  Mutex::Locker l(heartbeat_lock);
+  ceph_assert(ceph_mutex_is_locked(map_lock));
+  ceph_assert(ceph_mutex_is_locked(mon_report_lock));
+  std::lock_guard l(heartbeat_lock);
   utime_t now = ceph_clock_now();
+  const auto osdmap = get_osdmap();
   while (!failure_queue.empty()) {
     int osd = failure_queue.begin()->first;
-    entity_inst_t i = osdmap->get_inst(osd);
     if (!failure_pending.count(osd)) {
       int failed_for = (int)(double)(now - failure_queue.begin()->second);
-      monc->send_mon_message(new MOSDFailure(monc->get_fsid(), i, failed_for,
-                                            osdmap->get_epoch()));
-      failure_pending[osd] = make_pair(failure_queue.begin()->second, i);
+      monc->send_mon_message(
+       new MOSDFailure(
+         monc->get_fsid(),
+         osd,
+         osdmap->get_addrs(osd),
+         failed_for,
+         osdmap->get_epoch()));
+      failure_pending[osd] = make_pair(failure_queue.begin()->second,
+                                      osdmap->get_addrs(osd));
     }
     failure_queue.erase(osd);
   }
 }
 
-void OSD::send_still_alive(epoch_t epoch, const entity_inst_t &i)
+void OSD::send_still_alive(epoch_t epoch, int osd, const entity_addrvec_t &addrs)
 {
-  MOSDFailure *m = new MOSDFailure(monc->get_fsid(), i, 0, epoch, MOSDFailure::FLAG_ALIVE);
+  MOSDFailure *m = new MOSDFailure(monc->get_fsid(), osd, addrs, 0, epoch,
+                                  MOSDFailure::FLAG_ALIVE);
   monc->send_mon_message(m);
 }
 
-void OSD::send_pg_stats(const utime_t &now)
+void OSD::cancel_pending_failures()
 {
-  assert(map_lock.is_locked());
-  dout(20) << "send_pg_stats" << dendl;
-
-  osd_stat_t cur_stat = service.get_osd_stat();
-
-  cur_stat.os_perf_stat = store->get_cur_stats();
-
-  pg_stat_queue_lock.Lock();
-
-  if (osd_stat_updated || !pg_stat_queue.empty()) {
-    last_pg_stats_sent = now;
-    osd_stat_updated = false;
-
-    dout(10) << "send_pg_stats - " << pg_stat_queue.size() << " pgs updated" << dendl;
-
-    utime_t had_for(now);
-    had_for -= had_map_since;
-
-    MPGStats *m = new MPGStats(monc->get_fsid(), osdmap->get_epoch(), had_for);
-
-    uint64_t tid = ++pg_stat_tid;
-    m->set_tid(tid);
-    m->osd_stat = cur_stat;
-
-    xlist<PG*>::iterator p = pg_stat_queue.begin();
-    while (!p.end()) {
-      PG *pg = *p;
-      ++p;
-      if (!pg->is_primary()) {  // we hold map_lock; role is stable.
-       pg->stat_queue_item.remove_myself();
-       pg->put("pg_stat_queue");
-       continue;
-      }
-      pg->pg_stats_publish_lock.Lock();
-      if (pg->pg_stats_publish_valid) {
-       m->pg_stat[pg->info.pgid.pgid] = pg->pg_stats_publish;
-       dout(25) << " sending " << pg->info.pgid << " " << pg->pg_stats_publish.reported_epoch << ":"
-                << pg->pg_stats_publish.reported_seq << dendl;
-      } else {
-       dout(25) << " NOT sending " << pg->info.pgid << " " << pg->pg_stats_publish.reported_epoch << ":"
-                << pg->pg_stats_publish.reported_seq << ", not valid" << dendl;
-      }
-      pg->pg_stats_publish_lock.Unlock();
-    }
-
-    if (last_pg_stats_ack == utime_t() || !outstanding_pg_stats.empty()) {
-      last_pg_stats_ack = ceph_clock_now();
-    }
-    outstanding_pg_stats.insert(tid);
-    dout(20) << __func__ << "  updates pending: " << outstanding_pg_stats << dendl;
-
-    monc->send_mon_message(m);
-  }
-
-  pg_stat_queue_lock.Unlock();
-}
-
-void OSD::handle_pg_stats_ack(MPGStatsAck *ack)
-{
-  dout(10) << "handle_pg_stats_ack " << dendl;
-
-  if (!require_mon_peer(ack)) {
-    ack->put();
-    return;
-  }
-
-  // NOTE: we may get replies from a previous mon even while
-  // outstanding_pg_stats is empty if reconnecting races with replies
-  // in flight.
-
-  pg_stat_queue_lock.Lock();
-
-  last_pg_stats_ack = ceph_clock_now();
-
-  // decay timeout slowly (analogous to TCP)
-  stats_ack_timeout =
-    MAX(cct->_conf->osd_mon_ack_timeout,
-       stats_ack_timeout * cct->_conf->osd_stats_ack_timeout_decay);
-  dout(20) << __func__ << "  timeout now " << stats_ack_timeout << dendl;
-
-  if (ack->get_tid() > pg_stat_tid_flushed) {
-    pg_stat_tid_flushed = ack->get_tid();
-    pg_stat_queue_cond.Signal();
-  }
-
-  xlist<PG*>::iterator p = pg_stat_queue.begin();
-  while (!p.end()) {
-    PG *pg = *p;
-    PGRef _pg(pg);
-    ++p;
-
-    auto acked = ack->pg_stat.find(pg->info.pgid.pgid);
-    if (acked != ack->pg_stat.end()) {
-      pg->pg_stats_publish_lock.Lock();
-      if (acked->second.first == pg->pg_stats_publish.reported_seq &&
-         acked->second.second == pg->pg_stats_publish.reported_epoch) {
-       dout(25) << " ack on " << pg->info.pgid << " " << pg->pg_stats_publish.reported_epoch
-                << ":" << pg->pg_stats_publish.reported_seq << dendl;
-       pg->stat_queue_item.remove_myself();
-       pg->put("pg_stat_queue");
-      } else {
-       dout(25) << " still pending " << pg->info.pgid << " " << pg->pg_stats_publish.reported_epoch
-                << ":" << pg->pg_stats_publish.reported_seq << " > acked "
-                << acked->second << dendl;
-      }
-      pg->pg_stats_publish_lock.Unlock();
-    } else {
-      dout(30) << " still pending " << pg->info.pgid << " " << pg->pg_stats_publish.reported_epoch
-              << ":" << pg->pg_stats_publish.reported_seq << dendl;
-    }
+  std::lock_guard l(heartbeat_lock);
+  auto it = failure_pending.begin();
+  while (it != failure_pending.end()) {
+    dout(10) << __func__ << " canceling in-flight failure report for osd."
+             << it->first << dendl;
+    send_still_alive(get_osdmap_epoch(), it->first, it->second.second);
+    failure_pending.erase(it++);
   }
-
-  outstanding_pg_stats.erase(ack->get_tid());
-  dout(20) << __func__ << "  still pending: " << outstanding_pg_stats << dendl;
-
-  pg_stat_queue_lock.Unlock();
-
-  ack->put();
-}
-
-void OSD::flush_pg_stats()
-{
-  dout(10) << "flush_pg_stats" << dendl;
-  osd_lock.Unlock();
-  utime_t now = ceph_clock_now();
-  map_lock.get_read();
-  mon_report_lock.Lock();
-  send_pg_stats(now);
-  mon_report_lock.Unlock();
-  map_lock.put_read();
-
-
-  pg_stat_queue_lock.Lock();
-  uint64_t tid = pg_stat_tid;
-  dout(10) << "flush_pg_stats waiting for stats tid " << tid << " to flush" << dendl;
-  while (tid > pg_stat_tid_flushed)
-    pg_stat_queue_cond.Wait(pg_stat_queue_lock);
-  dout(10) << "flush_pg_stats finished waiting for stats tid " << tid << " to flush" << dendl;
-  pg_stat_queue_lock.Unlock();
-
-  osd_lock.Lock();
 }
 
 void OSD::send_beacon(const ceph::coarse_mono_clock::time_point& now)
@@ -5917,12 +6845,15 @@ void OSD::send_beacon(const ceph::coarse_mono_clock::time_point& now)
       monmap.get_required_features().contains_all(
         ceph::features::mon::FEATURE_LUMINOUS)) {
     dout(20) << __func__ << " sending" << dendl;
-    last_sent_beacon = now;
     MOSDBeacon* beacon = nullptr;
     {
-      Mutex::Locker l{min_last_epoch_clean_lock};
-      beacon = new MOSDBeacon(osdmap->get_epoch(), min_last_epoch_clean);
-      std::swap(beacon->pgs, min_last_epoch_clean_pgs);
+      std::lock_guard l{min_last_epoch_clean_lock};
+      beacon = new MOSDBeacon(get_osdmap_epoch(),
+                             min_last_epoch_clean,
+                             superblock.last_purged_snaps_scrub,
+                             cct->_conf->osd_beacon_report_interval);
+      beacon->pgs = min_last_epoch_clean_pgs;
+      last_sent_beacon = now;
     }
     monc->send_mon_message(beacon);
   } else {
@@ -5930,540 +6861,133 @@ void OSD::send_beacon(const ceph::coarse_mono_clock::time_point& now)
   }
 }
 
-void OSD::handle_command(MMonCommand *m)
-{
-  if (!require_mon_peer(m)) {
-    m->put();
-    return;
-  }
-
-  Command *c = new Command(m->cmd, m->get_tid(), m->get_data(), NULL);
-  command_wq.queue(c);
-  m->put();
-}
-
 void OSD::handle_command(MCommand *m)
 {
   ConnectionRef con = m->get_connection();
-  Session *session = static_cast<Session *>(con->get_priv());
+  auto session = ceph::ref_cast<Session>(con->get_priv());
   if (!session) {
-    con->send_message(new MCommandReply(m, -EPERM));
+    con->send_message(new MCommandReply(m, -EACCES));
     m->put();
     return;
   }
-
-  OSDCap& caps = session->caps;
-  session->put();
-
-  if (!caps.allow_all() || m->get_source().is_mon()) {
-    con->send_message(new MCommandReply(m, -EPERM));
+  if (!session->caps.allow_all()) {
+    con->send_message(new MCommandReply(m, -EACCES));
     m->put();
     return;
   }
-
-  Command *c = new Command(m->cmd, m->get_tid(), m->get_data(), con.get());
-  command_wq.queue(c);
-
+  cct->get_admin_socket()->queue_tell_command(m);
   m->put();
 }
 
-struct OSDCommand {
-  string cmdstring;
-  string helpstring;
-  string module;
-  string perm;
-  string availability;
-} osd_commands[] = {
-
-#define COMMAND(parsesig, helptext, module, perm, availability) \
-  {parsesig, helptext, module, perm, availability},
-
-// yes, these are really pg commands, but there's a limit to how
-// much work it's worth.  The OSD returns all of them.  Make this
-// form (pg <pgid> <cmd>) valid only for the cli.
-// Rest uses "tell <pgid> <cmd>"
-
-COMMAND("pg " \
-       "name=pgid,type=CephPgid " \
-       "name=cmd,type=CephChoices,strings=query", \
-       "show details of a specific pg", "osd", "r", "cli")
-COMMAND("pg " \
-       "name=pgid,type=CephPgid " \
-       "name=cmd,type=CephChoices,strings=mark_unfound_lost " \
-       "name=mulcmd,type=CephChoices,strings=revert|delete", \
-       "mark all unfound objects in this pg as lost, either removing or reverting to a prior version if one is available",
-       "osd", "rw", "cli")
-COMMAND("pg " \
-       "name=pgid,type=CephPgid " \
-       "name=cmd,type=CephChoices,strings=list_missing " \
-       "name=offset,type=CephString,req=false",
-       "list missing objects on this pg, perhaps starting at an offset given in JSON",
-       "osd", "r", "cli")
-
-// new form: tell <pgid> <cmd> for both cli and rest
-
-COMMAND("query",
-       "show details of a specific pg", "osd", "r", "cli,rest")
-COMMAND("mark_unfound_lost " \
-       "name=mulcmd,type=CephChoices,strings=revert|delete", \
-       "mark all unfound objects in this pg as lost, either removing or reverting to a prior version if one is available",
-       "osd", "rw", "cli,rest")
-COMMAND("list_missing " \
-       "name=offset,type=CephString,req=false",
-       "list missing objects on this pg, perhaps starting at an offset given in JSON",
-       "osd", "r", "cli,rest")
-
-// tell <osd.n> commands.  Validation of osd.n must be special-cased in client
-COMMAND("version", "report version of OSD", "osd", "r", "cli,rest")
-COMMAND("get_command_descriptions", "list commands descriptions", "osd", "r", "cli,rest")
-COMMAND("injectargs " \
-       "name=injected_args,type=CephString,n=N",
-       "inject configuration arguments into running OSD",
-       "osd", "rw", "cli,rest")
-COMMAND("cluster_log " \
-       "name=level,type=CephChoices,strings=error,warning,info,debug " \
-       "name=message,type=CephString,n=N",
-       "log a message to the cluster log",
-       "osd", "rw", "cli,rest")
-COMMAND("bench " \
-       "name=count,type=CephInt,req=false " \
-       "name=size,type=CephInt,req=false " \
-       "name=object_size,type=CephInt,req=false " \
-       "name=object_num,type=CephInt,req=false ", \
-       "OSD benchmark: write <count> <size>-byte objects, " \
-       "(default 1G size 4MB). Results in log.",
-       "osd", "rw", "cli,rest")
-COMMAND("flush_pg_stats", "flush pg stats", "osd", "rw", "cli,rest")
-COMMAND("heap " \
-       "name=heapcmd,type=CephChoices,strings=dump|start_profiler|stop_profiler|release|stats", \
-       "show heap usage info (available only if compiled with tcmalloc)", \
-       "osd", "rw", "cli,rest")
-COMMAND("debug dump_missing " \
-       "name=filename,type=CephFilepath",
-       "dump missing objects to a named file", "osd", "r", "cli,rest")
-COMMAND("debug kick_recovery_wq " \
-       "name=delay,type=CephInt,range=0",
-       "set osd_recovery_delay_start to <val>", "osd", "rw", "cli,rest")
-COMMAND("cpu_profiler " \
-       "name=arg,type=CephChoices,strings=status|flush",
-       "run cpu profiling on daemon", "osd", "rw", "cli,rest")
-COMMAND("dump_pg_recovery_stats", "dump pg recovery statistics",
-       "osd", "r", "cli,rest")
-COMMAND("reset_pg_recovery_stats", "reset pg recovery statistics",
-       "osd", "rw", "cli,rest")
-};
-
-void OSD::do_command(Connection *con, ceph_tid_t tid, vector<string>& cmd, bufferlist& data)
-{
-  int r = 0;
-  stringstream ss, ds;
-  string rs;
-  bufferlist odata;
-
-  dout(20) << "do_command tid " << tid << " " << cmd << dendl;
-
-  map<string, cmd_vartype> cmdmap;
-  string prefix;
-  string format;
-  string pgidstr;
-  boost::scoped_ptr<Formatter> f;
-
-  if (cmd.empty()) {
-    ss << "no command given";
-    goto out;
-  }
-
-  if (!cmdmap_from_json(cmd, &cmdmap, ss)) {
-    r = -EINVAL;
-    goto out;
-  }
-
-  cmd_getval(cct, cmdmap, "prefix", prefix);
-
-  if (prefix == "get_command_descriptions") {
-    int cmdnum = 0;
-    JSONFormatter *f = new JSONFormatter();
-    f->open_object_section("command_descriptions");
-    for (OSDCommand *cp = osd_commands;
-        cp < &osd_commands[ARRAY_SIZE(osd_commands)]; cp++) {
-
-      ostringstream secname;
-      secname << "cmd" << setfill('0') << std::setw(3) << cmdnum;
-      dump_cmddesc_to_json(f, secname.str(), cp->cmdstring, cp->helpstring,
-                          cp->module, cp->perm, cp->availability, 0);
-      cmdnum++;
-    }
-    f->close_section();        // command_descriptions
-
-    f->flush(ds);
-    delete f;
-    goto out;
-  }
-
-  cmd_getval(cct, cmdmap, "format", format);
-  f.reset(Formatter::create(format));
-
-  if (prefix == "version") {
-    if (f) {
-      f->open_object_section("version");
-      f->dump_string("version", pretty_version_to_str());
-      f->close_section();
-      f->flush(ds);
-    } else {
-      ds << pretty_version_to_str();
-    }
-    goto out;
-  }
-  else if (prefix == "injectargs") {
-    vector<string> argsvec;
-    cmd_getval(cct, cmdmap, "injected_args", argsvec);
-
-    if (argsvec.empty()) {
-      r = -EINVAL;
-      ss << "ignoring empty injectargs";
-      goto out;
-    }
-    string args = argsvec.front();
-    for (vector<string>::iterator a = ++argsvec.begin(); a != argsvec.end(); ++a)
-      args += " " + *a;
-    osd_lock.Unlock();
-    r = cct->_conf->injectargs(args, &ss);
-    osd_lock.Lock();
-  }
-  else if (prefix == "cluster_log") {
-    vector<string> msg;
-    cmd_getval(cct, cmdmap, "message", msg);
-    if (msg.empty()) {
-      r = -EINVAL;
-      ss << "ignoring empty log message";
-      goto out;
-    }
-    string message = msg.front();
-    for (vector<string>::iterator a = ++msg.begin(); a != msg.end(); ++a)
-      message += " " + *a;
-    string lvl;
-    cmd_getval(cct, cmdmap, "level", lvl);
-    clog_type level = string_to_clog_type(lvl);
-    if (level < 0) {
-      r = -EINVAL;
-      ss << "unknown level '" << lvl << "'";
-      goto out;
-    }
-    clog->do_log(level, message);
-  }
-
-  // either 'pg <pgid> <command>' or
-  // 'tell <pgid>' (which comes in without any of that prefix)?
-
-  else if (prefix == "pg" ||
-           prefix == "query" ||
-           prefix == "mark_unfound_lost" ||
-           prefix == "list_missing"
-          ) {
-    pg_t pgid;
-
-    if (!cmd_getval(cct, cmdmap, "pgid", pgidstr)) {
-      ss << "no pgid specified";
-      r = -EINVAL;
-    } else if (!pgid.parse(pgidstr.c_str())) {
-      ss << "couldn't parse pgid '" << pgidstr << "'";
-      r = -EINVAL;
-    } else {
-      spg_t pcand;
-      PG *pg = nullptr;
-      if (osdmap->get_primary_shard(pgid, &pcand) &&
-         (pg = _lookup_lock_pg(pcand))) {
-       if (pg->is_primary()) {
-         // simulate pg <pgid> cmd= for pg->do-command
-         if (prefix != "pg")
-           cmd_putval(cct, cmdmap, "cmd", prefix);
-         r = pg->do_command(cmdmap, ss, data, odata, con, tid);
-         if (r == -EAGAIN) {
-           pg->unlock();
-           // don't reply, pg will do so async
-           return;
-         }
-       } else {
-         ss << "not primary for pgid " << pgid;
-
-         // send them the latest diff to ensure they realize the mapping
-         // has changed.
-         service.send_incremental_map(osdmap->get_epoch() - 1, con, osdmap);
-
-         // do not reply; they will get newer maps and realize they
-         // need to resend.
-         pg->unlock();
-         return;
-       }
-       pg->unlock();
-      } else {
-       ss << "i don't have pgid " << pgid;
-       r = -ENOENT;
-      }
-    }
-  }
-
-  else if (prefix == "bench") {
-    int64_t count;
-    int64_t bsize;
-    int64_t osize, onum;
-    // default count 1G, size 4MB
-    cmd_getval(cct, cmdmap, "count", count, (int64_t)1 << 30);
-    cmd_getval(cct, cmdmap, "size", bsize, (int64_t)4 << 20);
-    cmd_getval(cct, cmdmap, "object_size", osize, (int64_t)0);
-    cmd_getval(cct, cmdmap, "object_num", onum, (int64_t)0);
-
-    ceph::shared_ptr<ObjectStore::Sequencer> osr (std::make_shared<
-                                        ObjectStore::Sequencer>("bench"));
-
-    uint32_t duration = cct->_conf->osd_bench_duration;
-
-    if (bsize > (int64_t) cct->_conf->osd_bench_max_block_size) {
-      // let us limit the block size because the next checks rely on it
-      // having a sane value.  If we allow any block size to be set things
-      // can still go sideways.
-      ss << "block 'size' values are capped at "
-         << prettybyte_t(cct->_conf->osd_bench_max_block_size) << ". If you wish to use"
-         << " a higher value, please adjust 'osd_bench_max_block_size'";
-      r = -EINVAL;
-      goto out;
-    } else if (bsize < (int64_t) (1 << 20)) {
-      // entering the realm of small block sizes.
-      // limit the count to a sane value, assuming a configurable amount of
-      // IOPS and duration, so that the OSD doesn't get hung up on this,
-      // preventing timeouts from going off
-      int64_t max_count =
-        bsize * duration * cct->_conf->osd_bench_small_size_max_iops;
-      if (count > max_count) {
-        ss << "'count' values greater than " << max_count
-           << " for a block size of " << prettybyte_t(bsize) << ", assuming "
-           << cct->_conf->osd_bench_small_size_max_iops << " IOPS,"
-           << " for " << duration << " seconds,"
-           << " can cause ill effects on osd. "
-           << " Please adjust 'osd_bench_small_size_max_iops' with a higher"
-           << " value if you wish to use a higher 'count'.";
-        r = -EINVAL;
-        goto out;
-      }
-    } else {
-      // 1MB block sizes are big enough so that we get more stuff done.
-      // However, to avoid the osd from getting hung on this and having
-      // timers being triggered, we are going to limit the count assuming
-      // a configurable throughput and duration.
-      // NOTE: max_count is the total amount of bytes that we believe we
-      //       will be able to write during 'duration' for the given
-      //       throughput.  The block size hardly impacts this unless it's
-      //       way too big.  Given we already check how big the block size
-      //       is, it's safe to assume everything will check out.
-      int64_t max_count =
-        cct->_conf->osd_bench_large_size_max_throughput * duration;
-      if (count > max_count) {
-        ss << "'count' values greater than " << max_count
-           << " for a block size of " << prettybyte_t(bsize) << ", assuming "
-           << prettybyte_t(cct->_conf->osd_bench_large_size_max_throughput) << "/s,"
-           << " for " << duration << " seconds,"
-           << " can cause ill effects on osd. "
-           << " Please adjust 'osd_bench_large_size_max_throughput'"
-           << " with a higher value if you wish to use a higher 'count'.";
-        r = -EINVAL;
-        goto out;
-      }
-    }
-
-    if (osize && bsize > osize)
-      bsize = osize;
-
-    dout(1) << " bench count " << count
-            << " bsize " << prettybyte_t(bsize) << dendl;
-
-    ObjectStore::Transaction cleanupt;
-
-    if (osize && onum) {
-      bufferlist bl;
-      bufferptr bp(osize);
-      bp.zero();
-      bl.push_back(std::move(bp));
-      bl.rebuild_page_aligned();
-      for (int i=0; i<onum; ++i) {
-       char nm[30];
-       snprintf(nm, sizeof(nm), "disk_bw_test_%d", i);
-       object_t oid(nm);
-       hobject_t soid(sobject_t(oid, 0));
-       ObjectStore::Transaction t;
-       t.write(coll_t(), ghobject_t(soid), 0, osize, bl);
-       store->queue_transaction(osr.get(), std::move(t), NULL);
-       cleanupt.remove(coll_t(), ghobject_t(soid));
-      }
-    }
-
-    bufferlist bl;
-    bufferptr bp(bsize);
-    bp.zero();
-    bl.push_back(std::move(bp));
-    bl.rebuild_page_aligned();
-
+namespace {
+  class unlock_guard {
+    ceph::mutex& m;
+  public:
+    explicit unlock_guard(ceph::mutex& mutex)
+      : m(mutex)
     {
-      C_SaferCond waiter;
-      if (!osr->flush_commit(&waiter)) {
-       waiter.wait();
-      }
+      m.unlock();
     }
-
-    utime_t start = ceph_clock_now();
-    for (int64_t pos = 0; pos < count; pos += bsize) {
-      char nm[30];
-      unsigned offset = 0;
-      if (onum && osize) {
-       snprintf(nm, sizeof(nm), "disk_bw_test_%d", (int)(rand() % onum));
-       offset = rand() % (osize / bsize) * bsize;
-      } else {
-       snprintf(nm, sizeof(nm), "disk_bw_test_%lld", (long long)pos);
-      }
-      object_t oid(nm);
-      hobject_t soid(sobject_t(oid, 0));
-      ObjectStore::Transaction t;
-      t.write(coll_t::meta(), ghobject_t(soid), offset, bsize, bl);
-      store->queue_transaction(osr.get(), std::move(t), NULL);
-      if (!onum || !osize)
-       cleanupt.remove(coll_t::meta(), ghobject_t(soid));
+    unlock_guard(unlock_guard&) = delete;
+    ~unlock_guard() {
+      m.lock();
     }
+  };
+}
 
-    {
-      C_SaferCond waiter;
-      if (!osr->flush_commit(&waiter)) {
-       waiter.wait();
-      }
+void OSD::scrub_purged_snaps()
+{
+  dout(10) << __func__ << dendl;
+  ceph_assert(ceph_mutex_is_locked(osd_lock));
+  SnapMapper::Scrubber s(cct, store, service.meta_ch,
+                        make_snapmapper_oid(),
+                        make_purged_snaps_oid());
+  clog->debug() << "purged_snaps scrub starts";
+  osd_lock.unlock();
+  s.run();
+  if (s.stray.size()) {
+    clog->debug() << "purged_snaps scrub found " << s.stray.size() << " strays";
+  } else {
+    clog->debug() << "purged_snaps scrub ok";
+  }
+  set<pair<spg_t,snapid_t>> queued;
+  for (auto& [pool, snap, hash, shard] : s.stray) {
+    const pg_pool_t *pi = get_osdmap()->get_pg_pool(pool);
+    if (!pi) {
+      dout(20) << __func__ << " pool " << pool << " dne" << dendl;
+      continue;
     }
-    utime_t end = ceph_clock_now();
-
-    // clean up
-    store->queue_transaction(osr.get(), std::move(cleanupt), NULL);
-    {
-      C_SaferCond waiter;
-      if (!osr->flush_commit(&waiter)) {
-       waiter.wait();
-      }
+    pg_t pgid(pi->raw_hash_to_pg(hash), pool);
+    spg_t spgid(pgid, shard);
+    pair<spg_t,snapid_t> p(spgid, snap);
+    if (queued.count(p)) {
+      dout(20) << __func__ << " pg " << spgid << " snap " << snap
+              << " already queued" << dendl;
+      continue;
     }
-
-    uint64_t rate = (double)count / (end - start);
-    if (f) {
-      f->open_object_section("osd_bench_results");
-      f->dump_int("bytes_written", count);
-      f->dump_int("blocksize", bsize);
-      f->dump_unsigned("bytes_per_sec", rate);
-      f->close_section();
-      f->flush(ss);
-    } else {
-      ss << "bench: wrote " << prettybyte_t(count)
-        << " in blocks of " << prettybyte_t(bsize) << " in "
-        << (end-start) << " sec at " << prettybyte_t(rate) << "/sec";
+    PGRef pg = lookup_lock_pg(spgid);
+    if (!pg) {
+      dout(20) << __func__ << " pg " << spgid << " not found" << dendl;
+      continue;
     }
+    queued.insert(p);
+    dout(10) << __func__ << " requeue pg " << spgid << " " << pg << " snap "
+            << snap << dendl;
+    pg->queue_snap_retrim(snap);
+    pg->unlock();
   }
-
-  else if (prefix == "flush_pg_stats") {
-    flush_pg_stats();
+  osd_lock.lock();
+  if (is_stopping()) {
+    return;
   }
-
-  else if (prefix == "heap") {
-    r = ceph::osd_cmds::heap(*cct, cmdmap, *f, ds);
+  dout(10) << __func__ << " done queueing pgs, updating superblock" << dendl;
+  ObjectStore::Transaction t;
+  superblock.last_purged_snaps_scrub = ceph_clock_now();
+  write_superblock(t);
+  int tr = store->queue_transaction(service.meta_ch, std::move(t), nullptr);
+  ceph_assert(tr == 0);
+  if (is_active()) {
+    send_beacon(ceph::coarse_mono_clock::now());
   }
+  dout(10) << __func__ << " done" << dendl;
+}
 
-  else if (prefix == "debug dump_missing") {
-    string file_name;
-    cmd_getval(cct, cmdmap, "filename", file_name);
-    std::ofstream fout(file_name.c_str());
-    if (!fout.is_open()) {
-       ss << "failed to open file '" << file_name << "'";
-       r = -EINVAL;
-       goto out;
-    }
+void OSD::probe_smart(const string& only_devid, ostream& ss)
+{
+  set<string> devnames;
+  store->get_devices(&devnames);
+  uint64_t smart_timeout = cct->_conf.get_val<uint64_t>(
+    "osd_smart_report_timeout");
 
-    fout << "*** osd " << whoami << ": dump_missing ***" << std::endl;
-    RWLock::RLocker l(pg_map_lock);
-    for (ceph::unordered_map<spg_t, PG*>::const_iterator pg_map_e = pg_map.begin();
-        pg_map_e != pg_map.end(); ++pg_map_e) {
-      PG *pg = pg_map_e->second;
-      pg->lock();
+  // == typedef std::map<std::string, mValue> mObject;
+  json_spirit::mObject json_map;
 
-      fout << *pg << std::endl;
-      std::map<hobject_t, pg_missing_item>::const_iterator mend =
-       pg->pg_log.get_missing().get_items().end();
-      std::map<hobject_t, pg_missing_item>::const_iterator mi =
-       pg->pg_log.get_missing().get_items().begin();
-      for (; mi != mend; ++mi) {
-       fout << mi->first << " -> " << mi->second << std::endl;
-       if (!pg->missing_loc.needs_recovery(mi->first))
-         continue;
-       if (pg->missing_loc.is_unfound(mi->first))
-         fout << " unfound ";
-       const set<pg_shard_t> &mls(pg->missing_loc.get_locations(mi->first));
-       if (mls.empty())
-         continue;
-       fout << "missing_loc: " << mls << std::endl;
-      }
-      pg->unlock();
-      fout << std::endl;
+  for (auto dev : devnames) {
+    // smartctl works only on physical devices; filter out any logical device
+    if (dev.find("dm-") == 0) {
+      continue;
     }
 
-    fout.close();
-  }
-  else if (prefix == "debug kick_recovery_wq") {
-    int64_t delay;
-    cmd_getval(cct, cmdmap, "delay", delay);
-    ostringstream oss;
-    oss << delay;
-    r = cct->_conf->set_val("osd_recovery_delay_start", oss.str().c_str());
-    if (r != 0) {
-      ss << "kick_recovery_wq: error setting "
-        << "osd_recovery_delay_start to '" << delay << "': error "
-        << r;
-      goto out;
+    string err;
+    string devid = get_device_id(dev, &err);
+    if (devid.size() == 0) {
+      dout(10) << __func__ << " no unique id for dev " << dev << " ("
+              << err << "), skipping" << dendl;
+      continue;
     }
-    cct->_conf->apply_changes(NULL);
-    ss << "kicking recovery queue. set osd_recovery_delay_start "
-       << "to " << cct->_conf->osd_recovery_delay_start;
-  }
-
-  else if (prefix == "cpu_profiler") {
-    string arg;
-    cmd_getval(cct, cmdmap, "arg", arg);
-    vector<string> argvec;
-    get_str_vec(arg, argvec);
-    cpu_profiler_handle_command(argvec, ds);
-  }
-
-  else if (prefix == "dump_pg_recovery_stats") {
-    stringstream s;
-    if (f) {
-      pg_recovery_stats.dump_formatted(f.get());
-      f->flush(ds);
-    } else {
-      pg_recovery_stats.dump(s);
-      ds << "dump pg recovery stats: " << s.str();
+    if (only_devid.size() && devid != only_devid) {
+      continue;
     }
-  }
-
-  else if (prefix == "reset_pg_recovery_stats") {
-    ss << "reset pg recovery stats";
-    pg_recovery_stats.reset();
-  }
 
-  else {
-    ss << "unrecognized command! " << cmd;
-    r = -EINVAL;
-  }
-
- out:
-  rs = ss.str();
-  odata.append(ds);
-  dout(0) << "do_command r=" << r << " " << rs << dendl;
-  clog->info() << rs;
-  if (con) {
-    MCommandReply *reply = new MCommandReply(r, rs);
-    reply->set_tid(tid);
-    reply->set_data(odata);
-    con->send_message(reply);
+    json_spirit::mValue smart_json;
+    if (block_device_get_metrics(dev, smart_timeout,
+                                &smart_json)) {
+      dout(10) << "block_device_get_metrics failed for /dev/" << dev << dendl;
+      continue;
+    }
+    json_map[devid] = smart_json;
   }
+  json_spirit::write(json_map, ss, json_spirit::pretty_print);
 }
 
 bool OSD::heartbeat_dispatch(Message *m)
@@ -6499,9 +7023,9 @@ bool OSD::ms_dispatch(Message *m)
 
   // lock!
 
-  osd_lock.Lock();
+  osd_lock.lock();
   if (is_stopping()) {
-    osd_lock.Unlock();
+    osd_lock.unlock();
     m->put();
     return true;
   }
@@ -6509,52 +7033,63 @@ bool OSD::ms_dispatch(Message *m)
   do_waiters();
   _dispatch(m);
 
-  osd_lock.Unlock();
+  osd_lock.unlock();
 
   return true;
 }
 
-void OSD::maybe_share_map(
-  Session *session,
-  OpRequestRef op,
-  OSDMapRef osdmap)
+void OSDService::maybe_share_map(
+  Connection *con,
+  const OSDMapRef& osdmap,
+  epoch_t peer_epoch_lb)
 {
-  if (!op->check_send_map) {
+  // NOTE: we assume caller hold something that keeps the Connection itself
+  // pinned (e.g., an OpRequest's MessageRef).
+  auto session = ceph::ref_cast<Session>(con->get_priv());
+  if (!session) {
     return;
   }
-  epoch_t last_sent_epoch = 0;
 
+  // assume the peer has the newer of the op's sent_epoch and what
+  // we think we sent them.
   session->sent_epoch_lock.lock();
-  last_sent_epoch = session->last_sent_epoch;
+  if (peer_epoch_lb > session->last_sent_epoch) {
+    dout(10) << __func__ << " con " << con
+            << " " << con->get_peer_addr()
+            << " map epoch " << session->last_sent_epoch
+            << " -> " << peer_epoch_lb << " (as per caller)" << dendl;
+    session->last_sent_epoch = peer_epoch_lb;
+  }
+  epoch_t last_sent_epoch = session->last_sent_epoch;
   session->sent_epoch_lock.unlock();
 
-  const Message *m = op->get_req();
-  service.share_map(
-    m->get_source(),
-    m->get_connection().get(),
-    op->sent_epoch,
-    osdmap,
-    session ? &last_sent_epoch : NULL);
+  if (osdmap->get_epoch() <= last_sent_epoch) {
+    return;
+  }
+
+  send_incremental_map(last_sent_epoch, con, osdmap);
+  last_sent_epoch = osdmap->get_epoch();
 
   session->sent_epoch_lock.lock();
   if (session->last_sent_epoch < last_sent_epoch) {
+    dout(10) << __func__ << " con " << con
+            << " " << con->get_peer_addr()
+            << " map epoch " << session->last_sent_epoch
+            << " -> " << last_sent_epoch << " (shared)" << dendl;
     session->last_sent_epoch = last_sent_epoch;
   }
   session->sent_epoch_lock.unlock();
-
-  op->check_send_map = false;
 }
 
-void OSD::dispatch_session_waiting(Session *session, OSDMapRef osdmap)
+void OSD::dispatch_session_waiting(const ceph::ref_t<Session>& session, OSDMapRef osdmap)
 {
-  assert(session->session_dispatch_lock.is_locked());
+  ceph_assert(ceph_mutex_is_locked(session->session_dispatch_lock));
 
   auto i = session->waiting_on_map.begin();
   while (i != session->waiting_on_map.end()) {
     OpRequestRef op = &(*i);
-    assert(ms_can_fast_dispatch(op->get_req()));
-    const MOSDFastDispatchOp *m = static_cast<const MOSDFastDispatchOp*>(
-      op->get_req());
+    ceph_assert(ms_can_fast_dispatch(op->get_req()));
+    auto m = op->get_req<MOSDFastDispatchOp>();
     if (m->get_min_epoch() > osdmap->get_epoch()) {
       break;
     }
@@ -6571,7 +7106,7 @@ void OSD::dispatch_session_waiting(Session *session, OSDMapRef osdmap)
     } else {
       pgid = m->get_spg();
     }
-    enqueue_op(pgid, op, m->get_map_epoch());
+    enqueue_op(pgid, std::move(op), m->get_map_epoch());
   }
 
   if (session->waiting_on_map.empty()) {
@@ -6583,165 +7118,164 @@ void OSD::dispatch_session_waiting(Session *session, OSDMapRef osdmap)
 
 void OSD::ms_fast_dispatch(Message *m)
 {
-  FUNCTRACE();
+
+#ifdef HAVE_JAEGER
+  jaeger_tracing::init_tracer("osd-services-reinit");
+  dout(10) << "jaeger tracer after " << opentracing::Tracer::Global() << dendl;
+  auto dispatch_span = jaeger_tracing::new_span(__func__);
+#endif
+  FUNCTRACE(cct);
   if (service.is_stopping()) {
     m->put();
     return;
   }
-  OpRequestRef op = op_tracker.create_request<OpRequest, Message*>(m);
-  {
-#ifdef WITH_LTTNG
-    osd_reqid_t reqid = op->get_reqid();
-#endif
-    tracepoint(osd, ms_fast_dispatch, reqid.name._type,
-        reqid.name._num, reqid.tid, reqid.inc);
-  }
-
-  if (m->trace)
-    op->osd_trace.init("osd op", &trace_endpoint, &m->trace);
-
-  // note sender epoch, min req'd epoch
-  op->sent_epoch = static_cast<MOSDFastDispatchOp*>(m)->get_map_epoch();
-  op->min_epoch = static_cast<MOSDFastDispatchOp*>(m)->get_min_epoch();
-  assert(op->min_epoch <= op->sent_epoch); // sanity check!
 
-  service.maybe_inject_dispatch_delay();
+  // peering event?
+  switch (m->get_type()) {
+  case CEPH_MSG_PING:
+    dout(10) << "ping from " << m->get_source() << dendl;
+    m->put();
+    return;
+  case MSG_OSD_FORCE_RECOVERY:
+    handle_fast_force_recovery(static_cast<MOSDForceRecovery*>(m));
+    return;
+  case MSG_OSD_SCRUB2:
+    handle_fast_scrub(static_cast<MOSDScrub2*>(m));
+    return;
 
-  if (m->get_connection()->has_features(CEPH_FEATUREMASK_RESEND_ON_SPLIT) ||
-      m->get_type() != CEPH_MSG_OSD_OP) {
-    // queue it directly
-    enqueue_op(
-      static_cast<MOSDFastDispatchOp*>(m)->get_spg(),
-      op,
-      static_cast<MOSDFastDispatchOp*>(m)->get_map_epoch());
-  } else {
-    // legacy client, and this is an MOSDOp (the *only* fast dispatch
-    // message that didn't have an explicit spg_t); we need to map
-    // them to an spg_t while preserving delivery order.
-    Session *session = static_cast<Session*>(m->get_connection()->get_priv());
-    if (session) {
-      {
-       Mutex::Locker l(session->session_dispatch_lock);
-       op->get();
-       session->waiting_on_map.push_back(*op);
-       OSDMapRef nextmap = service.get_nextmap_reserved();
-       dispatch_session_waiting(session, nextmap);
-       service.release_map(nextmap);
-      }
-      session->put();
-    }
-  }
-  OID_EVENT_TRACE_WITH_MSG(m, "MS_FAST_DISPATCH_END", false); 
-}
+  case MSG_OSD_PG_CREATE2:
+    return handle_fast_pg_create(static_cast<MOSDPGCreate2*>(m));
+  case MSG_OSD_PG_QUERY:
+    return handle_fast_pg_query(static_cast<MOSDPGQuery*>(m));
+  case MSG_OSD_PG_NOTIFY:
+    return handle_fast_pg_notify(static_cast<MOSDPGNotify*>(m));
+  case MSG_OSD_PG_INFO:
+    return handle_fast_pg_info(static_cast<MOSDPGInfo*>(m));
+  case MSG_OSD_PG_REMOVE:
+    return handle_fast_pg_remove(static_cast<MOSDPGRemove*>(m));
 
-void OSD::ms_fast_preprocess(Message *m)
-{
-  if (m->get_connection()->get_peer_type() == CEPH_ENTITY_TYPE_OSD) {
-    if (m->get_type() == CEPH_MSG_OSD_MAP) {
-      MOSDMap *mm = static_cast<MOSDMap*>(m);
-      Session *s = static_cast<Session*>(m->get_connection()->get_priv());
-      if (s) {
-       s->received_map_lock.lock();
-       s->received_map_epoch = mm->get_last();
-       s->received_map_lock.unlock();
-       s->put();
+    // these are single-pg messages that handle themselves
+  case MSG_OSD_PG_LOG:
+  case MSG_OSD_PG_TRIM:
+  case MSG_OSD_PG_NOTIFY2:
+  case MSG_OSD_PG_QUERY2:
+  case MSG_OSD_PG_INFO2:
+  case MSG_OSD_BACKFILL_RESERVE:
+  case MSG_OSD_RECOVERY_RESERVE:
+  case MSG_OSD_PG_LEASE:
+  case MSG_OSD_PG_LEASE_ACK:
+    {
+      MOSDPeeringOp *pm = static_cast<MOSDPeeringOp*>(m);
+      if (require_osd_peer(pm)) {
+       enqueue_peering_evt(
+         pm->get_spg(),
+         PGPeeringEventRef(pm->get_event()));
       }
-    }
-  }
-}
-
-bool OSD::ms_get_authorizer(int dest_type, AuthAuthorizer **authorizer, bool force_new)
-{
-  dout(10) << "OSD::ms_get_authorizer type=" << ceph_entity_type_name(dest_type) << dendl;
-
-  if (dest_type == CEPH_ENTITY_TYPE_MON)
-    return true;
-
-  if (force_new) {
-    /* the MonClient checks keys every tick(), so we should just wait for that cycle
-       to get through */
-    if (monc->wait_auth_rotating(10) < 0) {
-      derr << "OSD::ms_get_authorizer wait_auth_rotating failed" << dendl;
-      return false;
+      pm->put();
+      return;
     }
   }
 
-  *authorizer = monc->build_authorizer(dest_type);
-  return *authorizer != NULL;
-}
-
-
-bool OSD::ms_verify_authorizer(Connection *con, int peer_type,
-                              int protocol, bufferlist& authorizer_data, bufferlist& authorizer_reply,
-                              bool& isvalid, CryptoKey& session_key)
-{
-  AuthAuthorizeHandler *authorize_handler = 0;
-  switch (peer_type) {
-  case CEPH_ENTITY_TYPE_MDS:
-    /*
-     * note: mds is technically a client from our perspective, but
-     * this makes the 'cluster' consistent w/ monitor's usage.
-     */
-  case CEPH_ENTITY_TYPE_OSD:
-  case CEPH_ENTITY_TYPE_MGR:
-    authorize_handler = authorize_handler_cluster_registry->get_handler(protocol);
-    break;
-  default:
-    authorize_handler = authorize_handler_service_registry->get_handler(protocol);
+  OpRequestRef op = op_tracker.create_request<OpRequest, Message*>(m);
+  {
+#ifdef WITH_LTTNG
+    osd_reqid_t reqid = op->get_reqid();
+#endif
+    tracepoint(osd, ms_fast_dispatch, reqid.name._type,
+        reqid.name._num, reqid.tid, reqid.inc);
   }
-  if (!authorize_handler) {
-    dout(0) << "No AuthAuthorizeHandler found for protocol " << protocol << dendl;
-    isvalid = false;
-    return true;
+#ifdef HAVE_JAEGER
+  op->set_osd_parent_span(dispatch_span);
+  if (op->osd_parent_span) {
+    auto op_req_span = jaeger_tracing::child_span("op-request-created", op->osd_parent_span);
+    op->set_osd_parent_span(op_req_span);
   }
+#endif
+  if (m->trace)
+    op->osd_trace.init("osd op", &trace_endpoint, &m->trace);
 
-  AuthCapsInfo caps_info;
-  EntityName name;
-  uint64_t global_id;
-  uint64_t auid = CEPH_AUTH_UID_DEFAULT;
+  // note sender epoch, min req's epoch
+  op->sent_epoch = static_cast<MOSDFastDispatchOp*>(m)->get_map_epoch();
+  op->min_epoch = static_cast<MOSDFastDispatchOp*>(m)->get_min_epoch();
+  ceph_assert(op->min_epoch <= op->sent_epoch); // sanity check!
 
-  isvalid = authorize_handler->verify_authorizer(
-    cct, monc->rotating_secrets.get(),
-    authorizer_data, authorizer_reply, name, global_id, caps_info, session_key,
-    &auid);
+  service.maybe_inject_dispatch_delay();
 
-  if (isvalid) {
-    Session *s = static_cast<Session *>(con->get_priv());
-    if (!s) {
-      s = new Session(cct);
-      con->set_priv(s->get());
-      s->con = con;
-      dout(10) << " new session " << s << " con=" << s->con << " addr=" << s->con->get_peer_addr() << dendl;
-    }
-
-    s->entity_name = name;
-    if (caps_info.allow_all)
-      s->caps.set_allow_all();
-    s->auid = auid;
-
-    if (caps_info.caps.length() > 0) {
-      bufferlist::iterator p = caps_info.caps.begin();
-      string str;
-      try {
-       ::decode(str, p);
-      }
-      catch (buffer::error& e) {
-      }
+  if (m->get_connection()->has_features(CEPH_FEATUREMASK_RESEND_ON_SPLIT) ||
+      m->get_type() != CEPH_MSG_OSD_OP) {
+    // queue it directly
+    enqueue_op(
+      static_cast<MOSDFastDispatchOp*>(m)->get_spg(),
+      std::move(op),
+      static_cast<MOSDFastDispatchOp*>(m)->get_map_epoch());
+  } else {
+    // legacy client, and this is an MOSDOp (the *only* fast dispatch
+    // message that didn't have an explicit spg_t); we need to map
+    // them to an spg_t while preserving delivery order.
+    auto priv = m->get_connection()->get_priv();
+    if (auto session = static_cast<Session*>(priv.get()); session) {
+      std::lock_guard l{session->session_dispatch_lock};
+      op->get();
+      session->waiting_on_map.push_back(*op);
+      OSDMapRef nextmap = service.get_nextmap_reserved();
+      dispatch_session_waiting(session, nextmap);
+      service.release_map(nextmap);
+    }
+  }
+  OID_EVENT_TRACE_WITH_MSG(m, "MS_FAST_DISPATCH_END", false);
+}
+
+int OSD::ms_handle_authentication(Connection *con)
+{
+  int ret = 0;
+  auto s = ceph::ref_cast<Session>(con->get_priv());
+  if (!s) {
+    s = ceph::make_ref<Session>(cct, con);
+    con->set_priv(s);
+    s->entity_name = con->get_peer_entity_name();
+    dout(10) << __func__ << " new session " << s << " con " << s->con
+            << " entity " << s->entity_name
+            << " addr " << con->get_peer_addrs() << dendl;
+  } else {
+    dout(10) << __func__ << " existing session " << s << " con " << s->con
+            << " entity " << s->entity_name
+            << " addr " << con->get_peer_addrs() << dendl;
+  }
+
+  AuthCapsInfo &caps_info = con->get_peer_caps_info();
+  if (caps_info.allow_all) {
+    s->caps.set_allow_all();
+  } else if (caps_info.caps.length() > 0) {
+    bufferlist::const_iterator p = caps_info.caps.cbegin();
+    string str;
+    try {
+      decode(str, p);
+    }
+    catch (ceph::buffer::error& e) {
+      dout(10) << __func__ << " session " << s << " " << s->entity_name
+              << " failed to decode caps string" << dendl;
+      ret = -EACCES;
+    }
+    if (!ret) {
       bool success = s->caps.parse(str);
-      if (success)
-       dout(10) << " session " << s << " " << s->entity_name << " has caps " << s->caps << " '" << str << "'" << dendl;
-      else
-       dout(10) << " session " << s << " " << s->entity_name << " failed to parse caps '" << str << "'" << dendl;
+      if (success) {
+       dout(10) << __func__ << " session " << s
+                << " " << s->entity_name
+                << " has caps " << s->caps << " '" << str << "'" << dendl;
+       ret = 1;
+      } else {
+       dout(10) << __func__ << " session " << s << " " << s->entity_name
+                << " failed to parse caps '" << str << "'" << dendl;
+       ret = -EACCES;
+      }
     }
-
-    s->put();
   }
-  return true;
+  return ret;
 }
 
 void OSD::do_waiters()
 {
-  assert(osd_lock.is_locked());
+  ceph_assert(ceph_mutex_is_locked(osd_lock));
 
   dout(10) << "do_waiters -- start" << dendl;
   while (!finished.empty()) {
@@ -6759,86 +7293,43 @@ void OSD::dispatch_op(OpRequestRef op)
   case MSG_OSD_PG_CREATE:
     handle_pg_create(op);
     break;
-  case MSG_OSD_PG_NOTIFY:
-    handle_pg_notify(op);
-    break;
-  case MSG_OSD_PG_QUERY:
-    handle_pg_query(op);
-    break;
-  case MSG_OSD_PG_LOG:
-    handle_pg_log(op);
-    break;
-  case MSG_OSD_PG_REMOVE:
-    handle_pg_remove(op);
-    break;
-  case MSG_OSD_PG_INFO:
-    handle_pg_info(op);
-    break;
-  case MSG_OSD_PG_TRIM:
-    handle_pg_trim(op);
-    break;
-  case MSG_OSD_BACKFILL_RESERVE:
-    handle_pg_backfill_reserve(op);
-    break;
-  case MSG_OSD_RECOVERY_RESERVE:
-    handle_pg_recovery_reserve(op);
-    break;
   }
 }
 
 void OSD::_dispatch(Message *m)
 {
-  assert(osd_lock.is_locked());
+  ceph_assert(ceph_mutex_is_locked(osd_lock));
   dout(20) << "_dispatch " << m << " " << *m << dendl;
 
   switch (m->get_type()) {
-
-    // -- don't need lock --
-  case CEPH_MSG_PING:
-    dout(10) << "ping from " << m->get_source() << dendl;
-    m->put();
-    break;
-
     // -- don't need OSDMap --
 
     // map and replication
   case CEPH_MSG_OSD_MAP:
     handle_osd_map(static_cast<MOSDMap*>(m));
     break;
+  case MSG_MON_GET_PURGED_SNAPS_REPLY:
+    handle_get_purged_snaps_reply(static_cast<MMonGetPurgedSnapsReply*>(m));
+    break;
 
     // osd
-  case MSG_PGSTATSACK:
-    handle_pg_stats_ack(static_cast<MPGStatsAck*>(m));
+  case MSG_OSD_SCRUB:
+    handle_scrub(static_cast<MOSDScrub*>(m));
     break;
 
-  case MSG_MON_COMMAND:
-    handle_command(static_cast<MMonCommand*>(m));
-    break;
   case MSG_COMMAND:
     handle_command(static_cast<MCommand*>(m));
-    break;
-
-  case MSG_OSD_SCRUB:
-    handle_scrub(static_cast<MOSDScrub*>(m));
-    break;
+    return;
 
     // -- need OSDMap --
 
   case MSG_OSD_PG_CREATE:
-  case MSG_OSD_PG_NOTIFY:
-  case MSG_OSD_PG_QUERY:
-  case MSG_OSD_PG_LOG:
-  case MSG_OSD_PG_REMOVE:
-  case MSG_OSD_PG_INFO:
-  case MSG_OSD_PG_TRIM:
-  case MSG_OSD_BACKFILL_RESERVE:
-  case MSG_OSD_RECOVERY_RESERVE:
     {
       OpRequestRef op = op_tracker.create_request<OpRequest, Message*>(m);
       if (m->trace)
         op->osd_trace.init("osd op", &trace_endpoint, &m->trace);
       // no map?  starting up?
-      if (!osdmap) {
+      if (!get_osdmap()) {
         dout(7) << "no OSDMap, not booted" << dendl;
        logger->inc(l_osd_waiting_for_map);
         waiting_for_osdmap.push_back(op);
@@ -6852,20 +7343,7 @@ void OSD::_dispatch(Message *m)
   }
 }
 
-void OSD::handle_pg_scrub(MOSDScrub *m, PG *pg)
-{
-  pg->lock();
-  if (pg->is_primary()) {
-    pg->unreg_next_scrub();
-    pg->scrubber.must_scrub = true;
-    pg->scrubber.must_deep_scrub = m->deep || m->repair;
-    pg->scrubber.must_repair = m->repair;
-    pg->reg_next_scrub();
-    dout(10) << "marking " << *pg << " for scrub" << dendl;
-  }
-  pg->unlock();
-}
-
+// remove me post-nautilus
 void OSD::handle_scrub(MOSDScrub *m)
 {
   dout(10) << "handle_scrub " << *m << dendl;
@@ -6874,34 +7352,65 @@ void OSD::handle_scrub(MOSDScrub *m)
     return;
   }
   if (m->fsid != monc->get_fsid()) {
-    dout(0) << "handle_scrub fsid " << m->fsid << " != " << monc->get_fsid() << dendl;
+    dout(0) << "handle_scrub fsid " << m->fsid << " != " << monc->get_fsid()
+           << dendl;
     m->put();
     return;
   }
 
-  RWLock::RLocker l(pg_map_lock);
-  if (m->scrub_pgs.empty()) {
-    for (ceph::unordered_map<spg_t, PG*>::iterator p = pg_map.begin();
-        p != pg_map.end();
-        ++p)
-      handle_pg_scrub(m, p->second);
-  } else {
-    for (vector<pg_t>::iterator p = m->scrub_pgs.begin();
-        p != m->scrub_pgs.end();
-        ++p) {
+  vector<spg_t> spgs;
+  _get_pgids(&spgs);
+
+  if (!m->scrub_pgs.empty()) {
+    vector<spg_t> v;
+    for (auto pgid : m->scrub_pgs) {
       spg_t pcand;
-      if (osdmap->get_primary_shard(*p, &pcand)) {
-       auto pg_map_entry = pg_map.find(pcand);
-       if (pg_map_entry != pg_map.end()) {
-         handle_pg_scrub(m, pg_map_entry->second);
-       }
+      if (get_osdmap()->get_primary_shard(pgid, &pcand) &&
+         std::find(spgs.begin(), spgs.end(), pcand) != spgs.end()) {
+       v.push_back(pcand);
       }
     }
+    spgs.swap(v);
+  }
+
+  for (auto pgid : spgs) {
+    enqueue_peering_evt(
+      pgid,
+      PGPeeringEventRef(
+       std::make_shared<PGPeeringEvent>(
+         get_osdmap_epoch(),
+         get_osdmap_epoch(),
+         PeeringState::RequestScrub(m->deep, m->repair))));
   }
 
   m->put();
 }
 
+void OSD::handle_fast_scrub(MOSDScrub2 *m)
+{
+  dout(10) << __func__ <<  " " << *m << dendl;
+  if (!require_mon_or_mgr_peer(m)) {
+    m->put();
+    return;
+  }
+  if (m->fsid != monc->get_fsid()) {
+    dout(0) << __func__ << " fsid " << m->fsid << " != " << monc->get_fsid()
+           << dendl;
+    m->put();
+    return;
+  }
+  for (auto pgid : m->scrub_pgs) {
+    enqueue_peering_evt(
+      pgid,
+      PGPeeringEventRef(
+       std::make_shared<PGPeeringEvent>(
+         m->epoch,
+         m->epoch,
+         PeeringState::RequestScrub(m->deep, m->repair))));
+  }
+  m->put();
+}
+
 bool OSD::scrub_random_backoff()
 {
   bool coin_flip = (rand() / (double)RAND_MAX >=
@@ -6933,7 +7442,12 @@ OSDService::ScrubJob::ScrubJob(CephContext* cct,
     double r = rand() / (double)RAND_MAX;
     sched_time +=
       scrub_min_interval * cct->_conf->osd_scrub_interval_randomize_ratio * r;
-    deadline += scrub_max_interval;
+    if (scrub_max_interval == 0) {
+      deadline = utime_t();
+    } else {
+      deadline += scrub_max_interval;
+    }
+
   }
 }
 
@@ -6945,11 +7459,61 @@ bool OSDService::ScrubJob::ScrubJob::operator<(const OSDService::ScrubJob& rhs)
   return pgid < rhs.pgid;
 }
 
+void OSDService::dumps_scrub(ceph::Formatter *f)
+{
+  ceph_assert(f != nullptr);
+  std::lock_guard l(sched_scrub_lock);
+
+  f->open_array_section("scrubs");
+  for (const auto &i: sched_scrub_pg) {
+    f->open_object_section("scrub");
+    f->dump_stream("pgid") << i.pgid;
+    f->dump_stream("sched_time") << i.sched_time;
+    f->dump_stream("deadline") << i.deadline;
+    f->dump_bool("forced", i.sched_time == PgScrubber::scrub_must_stamp());
+    f->close_section();
+  }
+  f->close_section();
+}
+
+double OSD::scrub_sleep_time(bool must_scrub)
+{
+  if (must_scrub) {
+    return cct->_conf->osd_scrub_sleep;
+  }
+  utime_t now = ceph_clock_now();
+  if (scrub_time_permit(now)) {
+    return cct->_conf->osd_scrub_sleep;
+  }
+  double normal_sleep = cct->_conf->osd_scrub_sleep;
+  double extended_sleep = cct->_conf->osd_scrub_extended_sleep;
+  return std::max(extended_sleep, normal_sleep);
+}
+
 bool OSD::scrub_time_permit(utime_t now)
 {
   struct tm bdt;
   time_t tt = now.sec();
   localtime_r(&tt, &bdt);
+
+  bool day_permit = false;
+  if (cct->_conf->osd_scrub_begin_week_day < cct->_conf->osd_scrub_end_week_day) {
+    if (bdt.tm_wday >= cct->_conf->osd_scrub_begin_week_day && bdt.tm_wday < cct->_conf->osd_scrub_end_week_day) {
+      day_permit = true;
+    }
+  } else {
+    if (bdt.tm_wday >= cct->_conf->osd_scrub_begin_week_day || bdt.tm_wday < cct->_conf->osd_scrub_end_week_day) {
+      day_permit = true;
+    }
+  }
+
+  if (!day_permit) {
+    dout(20) << __func__ << " should run between week day " << cct->_conf->osd_scrub_begin_week_day
+            << " - " << cct->_conf->osd_scrub_end_week_day
+            << " now " << bdt.tm_wday << " = no" << dendl;
+    return false;
+  }
+
   bool time_permit = false;
   if (cct->_conf->osd_scrub_begin_hour < cct->_conf->osd_scrub_end_hour) {
     if (bdt.tm_hour >= cct->_conf->osd_scrub_begin_hour && bdt.tm_hour < cct->_conf->osd_scrub_end_hour) {
@@ -6960,14 +7524,14 @@ bool OSD::scrub_time_permit(utime_t now)
       time_permit = true;
     }
   }
-  if (!time_permit) {
+  if (time_permit) {
     dout(20) << __func__ << " should run between " << cct->_conf->osd_scrub_begin_hour
             << " - " << cct->_conf->osd_scrub_end_hour
-            << " now " << bdt.tm_hour << " = no" << dendl;
+            << " now " << bdt.tm_hour << " = yes" << dendl;
   } else {
     dout(20) << __func__ << " should run between " << cct->_conf->osd_scrub_begin_hour
             << " - " << cct->_conf->osd_scrub_end_hour
-            << " now " << bdt.tm_hour << " = yes" << dendl;
+            << " now " << bdt.tm_hour << " = no" << dendl;
   }
   return time_permit;
 }
@@ -6981,8 +7545,10 @@ bool OSD::scrub_load_below_threshold()
   }
 
   // allow scrub if below configured threshold
-  if (loadavgs[0] < cct->_conf->osd_scrub_load_threshold) {
-    dout(20) << __func__ << " loadavg " << loadavgs[0]
+  long cpus = sysconf(_SC_NPROCESSORS_ONLN);
+  double loadavg_per_cpu = cpus > 0 ? loadavgs[0] / cpus : loadavgs[0];
+  if (loadavg_per_cpu < cct->_conf->osd_scrub_load_threshold) {
+    dout(20) << __func__ << " loadavg per cpu " << loadavg_per_cpu
             << " < max " << cct->_conf->osd_scrub_load_threshold
             << " = yes" << dendl;
     return true;
@@ -7007,59 +7573,224 @@ bool OSD::scrub_load_below_threshold()
 
 void OSD::sched_scrub()
 {
+  dout(20) << __func__ << " sched_scrub starts" << dendl;
+
   // if not permitted, fail fast
-  if (!service.can_inc_scrubs_pending()) {
+  if (!service.can_inc_scrubs()) {
+    dout(20) << __func__ << ": OSD cannot inc scrubs" << dendl;
     return;
   }
+  bool allow_requested_repair_only = false;
+  if (service.is_recovery_active() && !cct->_conf->osd_scrub_during_recovery) {
+    if (!cct->_conf->osd_repair_during_recovery) {
+      dout(15) << __func__ << ": not scheduling scrubs due to active recovery" << dendl;
+      return;
+    }
+    dout(10) << __func__
+             << " will only schedule explicitly requested repair due to active recovery"
+             << dendl;
+    allow_requested_repair_only = true;
+  }
 
   utime_t now = ceph_clock_now();
   bool time_permit = scrub_time_permit(now);
   bool load_is_low = scrub_load_below_threshold();
   dout(20) << "sched_scrub load_is_low=" << (int)load_is_low << dendl;
 
-  OSDService::ScrubJob scrub;
-  if (service.first_scrub_stamp(&scrub)) {
+  OSDService::ScrubJob scrub_job;
+  if (service.first_scrub_stamp(&scrub_job)) {
     do {
-      dout(30) << "sched_scrub examine " << scrub.pgid << " at " << scrub.sched_time << dendl;
+      dout(30) << "sched_scrub examine " << scrub_job.pgid << " at " << scrub_job.sched_time << dendl;
 
-      if (scrub.sched_time > now) {
+      if (scrub_job.sched_time > now) {
        // save ourselves some effort
-       dout(10) << "sched_scrub " << scrub.pgid << " scheduled at " << scrub.sched_time
+       dout(20) << "sched_scrub " << scrub_job.pgid << " scheduled at " << scrub_job.sched_time
                 << " > " << now << dendl;
        break;
       }
 
-      if (!cct->_conf->osd_scrub_during_recovery && service.is_recovery_active()) {
-        dout(10) << __func__ << "not scheduling scrub of " << scrub.pgid << " due to active recovery ops" << dendl;
-        break;
+      if ((scrub_job.deadline.is_zero() || scrub_job.deadline >= now) && !(time_permit && load_is_low)) {
+        dout(15) << __func__ << " not scheduling scrub for " << scrub_job.pgid << " due to "
+                 << (!time_permit ? "time not permit" : "high load") << dendl;
+        continue;
       }
 
-      if ((scrub.deadline >= now) && !(time_permit && load_is_low)) {
-        dout(10) << __func__ << " not scheduling scrub for " << scrub.pgid << " due to "
-                 << (!time_permit ? "time not permit" : "high load") << dendl;
+      PGRef pg = _lookup_lock_pg(scrub_job.pgid);
+      if (!pg) {
+       dout(20) << __func__ << " pg  " << scrub_job.pgid << " not found" << dendl;
+       continue;
+      }
+
+      // This has already started, so go on to the next scrub job
+      if (pg->is_scrub_active()) {
+       pg->unlock();
+       dout(20) << __func__ << ": already in progress pgid " << scrub_job.pgid << dendl;
+       continue;
+      }
+      // Skip other kinds of scrubbing if only explicitly requested repairing is allowed
+      if (allow_requested_repair_only && !pg->m_planned_scrub.must_repair) {
+        pg->unlock();
+        dout(10) << __func__ << " skip " << scrub_job.pgid
+                 << " because repairing is not explicitly requested on it"
+                 << dendl;
         continue;
       }
 
-      PG *pg = _lookup_lock_pg(scrub.pgid);
+      // If it is reserving, let it resolve before going to the next scrub job
+      if (pg->m_scrubber->is_reserving()) {
+       pg->unlock();
+       dout(10) << __func__ << ": reserve in progress pgid " << scrub_job.pgid << dendl;
+       break;
+      }
+      dout(15) << "sched_scrub scrubbing " << scrub_job.pgid << " at " << scrub_job.sched_time
+              << (pg->get_must_scrub() ? ", explicitly requested" :
+                  (load_is_low ? ", load_is_low" : " deadline < now"))
+              << dendl;
+      if (pg->sched_scrub()) {
+       pg->unlock();
+        dout(10) << __func__ << " scheduled a scrub!" << " (~" << scrub_job.pgid << "~)" << dendl;
+       break;
+      }
+      pg->unlock();
+    } while (service.next_scrub_stamp(scrub_job, &scrub_job));
+  }
+  dout(20) << "sched_scrub done" << dendl;
+}
+
+void OSD::resched_all_scrubs()
+{
+  dout(10) << __func__ << ": start" << dendl;
+  const vector<spg_t> pgs = [this] {
+    vector<spg_t> pgs;
+    OSDService::ScrubJob job;
+    if (service.first_scrub_stamp(&job)) {
+      do {
+        pgs.push_back(job.pgid);
+      } while (service.next_scrub_stamp(job, &job));
+    }
+    return pgs;
+  }();
+  for (auto& pgid : pgs) {
+      dout(20) << __func__ << ": examine " << pgid << dendl;
+      PGRef pg = _lookup_lock_pg(pgid);
       if (!pg)
        continue;
-      if (pg->get_pgbackend()->scrub_supported() && pg->is_active()) {
-       dout(10) << "sched_scrub scrubbing " << scrub.pgid << " at " << scrub.sched_time
-                << (pg->scrubber.must_scrub ? ", explicitly requested" :
-                    (load_is_low ? ", load_is_low" : " deadline < now"))
-                << dendl;
-       if (pg->sched_scrub()) {
-         pg->unlock();
-         break;
-       }
+      if (!pg->m_planned_scrub.must_scrub && !pg->m_planned_scrub.need_auto) {
+        dout(15) << __func__ << ": reschedule " << pgid << dendl;
+        pg->on_info_history_change();
       }
       pg->unlock();
-    } while (service.next_scrub_stamp(scrub, &scrub));
   }
-  dout(20) << "sched_scrub done" << dendl;
+  dout(10) << __func__ << ": done" << dendl;
 }
 
+MPGStats* OSD::collect_pg_stats()
+{
+  // This implementation unconditionally sends every is_primary PG's
+  // stats every time we're called.  This has equivalent cost to the
+  // previous implementation's worst case where all PGs are busy and
+  // their stats are always enqueued for sending.
+  std::shared_lock l{map_lock};
+
+  osd_stat_t cur_stat = service.get_osd_stat();
+  cur_stat.os_perf_stat = store->get_cur_stats();
+
+  auto m = new MPGStats(monc->get_fsid(), get_osdmap_epoch());
+  m->osd_stat = cur_stat;
+
+  std::lock_guard lec{min_last_epoch_clean_lock};
+  min_last_epoch_clean = get_osdmap_epoch();
+  min_last_epoch_clean_pgs.clear();
+
+  std::set<int64_t> pool_set;
+  vector<PGRef> pgs;
+  _get_pgs(&pgs);
+  for (auto& pg : pgs) {
+    auto pool = pg->pg_id.pgid.pool();
+    pool_set.emplace((int64_t)pool);
+    if (!pg->is_primary()) {
+      continue;
+    }
+    pg->get_pg_stats([&](const pg_stat_t& s, epoch_t lec) {
+       m->pg_stat[pg->pg_id.pgid] = s;
+       min_last_epoch_clean = std::min(min_last_epoch_clean, lec);
+       min_last_epoch_clean_pgs.push_back(pg->pg_id.pgid);
+      });
+  }
+  store_statfs_t st;
+  bool per_pool_stats = false;
+  bool per_pool_omap_stats = false;
+  for (auto p : pool_set) {
+    int r = store->pool_statfs(p, &st, &per_pool_omap_stats);
+    if (r == -ENOTSUP) {
+      break;
+    } else {
+      assert(r >= 0);
+      m->pool_stat[p] = st;
+      per_pool_stats = true;
+    }
+  }
+
+  // indicate whether we are reporting per-pool stats
+  m->osd_stat.num_osds = 1;
+  m->osd_stat.num_per_pool_osds = per_pool_stats ? 1 : 0;
+  m->osd_stat.num_per_pool_omap_osds = per_pool_omap_stats ? 1 : 0;
 
+  return m;
+}
+
+vector<DaemonHealthMetric> OSD::get_health_metrics()
+{
+  vector<DaemonHealthMetric> metrics;
+  {
+    utime_t oldest_secs;
+    const utime_t now = ceph_clock_now();
+    auto too_old = now;
+    too_old -= cct->_conf.get_val<double>("osd_op_complaint_time");
+    int slow = 0;
+    TrackedOpRef oldest_op;
+    auto count_slow_ops = [&](TrackedOp& op) {
+      if (op.get_initiated() < too_old) {
+        stringstream ss;
+        ss << "slow request " << op.get_desc()
+           << " initiated "
+           << op.get_initiated()
+           << " currently "
+           << op.state_string();
+        lgeneric_subdout(cct,osd,20) << ss.str() << dendl;
+        clog->warn() << ss.str();
+       slow++;
+       if (!oldest_op || op.get_initiated() < oldest_op->get_initiated()) {
+         oldest_op = &op;
+       }
+       return true;
+      } else {
+       return false;
+      }
+    };
+    if (op_tracker.visit_ops_in_flight(&oldest_secs, count_slow_ops)) {
+      if (slow) {
+       derr << __func__ << " reporting " << slow << " slow ops, oldest is "
+            << oldest_op->get_desc() << dendl;
+      }
+      metrics.emplace_back(daemon_metric::SLOW_OPS, slow, oldest_secs);
+    } else {
+      // no news is not good news.
+      metrics.emplace_back(daemon_metric::SLOW_OPS, 0, 0);
+    }
+  }
+  {
+    std::lock_guard l(pending_creates_lock);
+    auto n_primaries = pending_creates_from_mon;
+    for (const auto& create : pending_creates_from_osd) {
+      if (create.second) {
+       n_primaries++;
+      }
+    }
+    metrics.emplace_back(daemon_metric::PENDING_CREATING_PGS, n_primaries);
+  }
+  return metrics;
+}
 
 // =====================================================
 // MAP
@@ -7068,7 +7799,7 @@ void OSD::wait_for_new_map(OpRequestRef op)
 {
   // ask?
   if (waiting_for_osdmap.empty()) {
-    osdmap_subscribe(osdmap->get_epoch() + 1, false);
+    osdmap_subscribe(get_osdmap_epoch() + 1, false);
   }
 
   logger->inc(l_osd_waiting_for_map);
@@ -7083,26 +7814,21 @@ void OSD::wait_for_new_map(OpRequestRef op)
 
 void OSD::note_down_osd(int peer)
 {
-  assert(osd_lock.is_locked());
-  cluster_messenger->mark_down(osdmap->get_cluster_addr(peer));
+  ceph_assert(ceph_mutex_is_locked(osd_lock));
+  cluster_messenger->mark_down_addrs(get_osdmap()->get_cluster_addrs(peer));
 
-  heartbeat_lock.Lock();
+  std::lock_guard l{heartbeat_lock};
   failure_queue.erase(peer);
   failure_pending.erase(peer);
   map<int,HeartbeatInfo>::iterator p = heartbeat_peers.find(peer);
   if (p != heartbeat_peers.end()) {
-    p->second.con_back->mark_down();
-    if (p->second.con_front) {
-      p->second.con_front->mark_down();
-    }
+    p->second.clear_mark_down();
     heartbeat_peers.erase(p);
   }
-  heartbeat_lock.Unlock();
 }
 
 void OSD::note_up_osd(int peer)
 {
-  service.forget_peer_epoch(peer, osdmap->get_epoch() - 1);
   heartbeat_set_peers_need_update();
 }
 
@@ -7118,25 +7844,14 @@ struct C_OnMapCommit : public Context {
   }
 };
 
-struct C_OnMapApply : public Context {
-  OSDService *service;
-  list<OSDMapRef> pinned_maps;
-  epoch_t e;
-  C_OnMapApply(OSDService *service,
-              const list<OSDMapRef> &pinned_maps,
-              epoch_t e)
-    : service(service), pinned_maps(pinned_maps), e(e) {}
-  void finish(int r) override {
-    service->clear_map_bl_cache_pins(e);
-  }
-};
-
 void OSD::osdmap_subscribe(version_t epoch, bool force_request)
 {
-  OSDMapRef osdmap = service.get_osdmap();
-  if (osdmap->get_epoch() >= epoch)
+  std::lock_guard l(osdmap_subscribe_lock);
+  if (latest_subscribed_epoch >= epoch && !force_request)
     return;
 
+  latest_subscribed_epoch = std::max<uint64_t>(epoch, latest_subscribed_epoch);
+
   if (monc->sub_want_increment("osdmap", epoch, CEPH_SUBSCRIBE_ONETIME) ||
       force_request) {
     monc->renew_subs();
@@ -7160,8 +7875,8 @@ void OSD::trim_maps(epoch_t oldest, int nreceived, bool skip_maps)
     if (num >= cct->_conf->osd_target_transaction_size && num >= nreceived) {
       service.publish_superblock(superblock);
       write_superblock(t);
-      int tr = store->queue_transaction(service.meta_osr.get(), std::move(t), nullptr);
-      assert(tr == 0);
+      int tr = store->queue_transaction(service.meta_ch, std::move(t), nullptr);
+      ceph_assert(tr == 0);
       num = 0;
       if (!skip_maps) {
        // skip_maps leaves us with a range of old maps if we fail to remove all
@@ -7176,22 +7891,56 @@ void OSD::trim_maps(epoch_t oldest, int nreceived, bool skip_maps)
   if (num > 0) {
     service.publish_superblock(superblock);
     write_superblock(t);
-    store->queue_transaction(service.meta_osr.get(), std::move(t), nullptr);
+    int tr = store->queue_transaction(service.meta_ch, std::move(t), nullptr);
+    ceph_assert(tr == 0);
   }
   // we should not remove the cached maps
-  assert(min <= service.map_cache.cached_key_lower_bound());
+  ceph_assert(min <= service.map_cache.cached_key_lower_bound());
 }
 
 void OSD::handle_osd_map(MOSDMap *m)
 {
-  assert(osd_lock.is_locked());
-  // Keep a ref in the list until we get the newly received map written
-  // onto disk. This is important because as long as the refs are alive,
-  // the OSDMaps will be pinned in the cache and we won't try to read it
-  // off of disk. Otherwise these maps will probably not stay in the cache,
-  // and reading those OSDMaps before they are actually written can result
-  // in a crash. 
-  list<OSDMapRef> pinned_maps;
+  // wait for pgs to catch up
+  {
+    // we extend the map cache pins to accomodate pgs slow to consume maps
+    // for some period, until we hit the max_lag_factor bound, at which point
+    // we block here to stop injesting more maps than they are able to keep
+    // up with.
+    epoch_t max_lag = cct->_conf->osd_map_cache_size *
+      m_osd_pg_epoch_max_lag_factor;
+    ceph_assert(max_lag > 0);
+    epoch_t osd_min = 0;
+    for (auto shard : shards) {
+      epoch_t min = shard->get_min_pg_epoch();
+      if (osd_min == 0 || min < osd_min) {
+       osd_min = min;
+      }
+    }
+    epoch_t osdmap_epoch = get_osdmap_epoch();
+    if (osd_min > 0 &&
+       osdmap_epoch > max_lag &&
+       osdmap_epoch - max_lag > osd_min) {
+      epoch_t need = osdmap_epoch - max_lag;
+      dout(10) << __func__ << " waiting for pgs to catch up (need " << need
+              << " max_lag " << max_lag << ")" << dendl;
+      for (auto shard : shards) {
+       epoch_t min = shard->get_min_pg_epoch();
+       if (need > min) {
+         dout(10) << __func__ << " waiting for pgs to consume " << need
+                  << " (shard " << shard->shard_id << " min " << min
+                  << ", map cache is " << cct->_conf->osd_map_cache_size
+                  << ", max_lag_factor " << m_osd_pg_epoch_max_lag_factor
+                  << ")" << dendl;
+         unlock_guard unlock{osd_lock};
+         shard->wait_min_pg_epoch(need);
+       }
+      }
+    }
+  }
+
+  ceph_assert(ceph_mutex_is_locked(osd_lock));
+  map<epoch_t,OSDMapRef> added_maps;
+  map<epoch_t,bufferlist> added_maps_bl;
   if (m->fsid != monc->get_fsid()) {
     dout(0) << "handle_osd_map fsid " << m->fsid << " != "
            << monc->get_fsid() << dendl;
@@ -7204,18 +7953,15 @@ void OSD::handle_osd_map(MOSDMap *m)
     return;
   }
 
-  Session *session = static_cast<Session *>(m->get_connection()->get_priv());
+  auto session = ceph::ref_cast<Session>(m->get_connection()->get_priv());
   if (session && !(session->entity_name.is_mon() ||
                   session->entity_name.is_osd())) {
     //not enough perms!
     dout(10) << "got osd map from Session " << session
              << " which we can't take maps from (not a mon or osd)" << dendl;
     m->put();
-    session->put();
     return;
   }
-  if (session)
-    session->put();
 
   // share with the objecter
   if (!is_preboot())
@@ -7234,7 +7980,7 @@ void OSD::handle_osd_map(MOSDMap *m)
     logger->inc(l_osd_mape_dup, superblock.newest_map - first + 1);
   if (service.max_oldest_map < m->oldest_map) {
     service.max_oldest_map = m->oldest_map;
-    assert(service.max_oldest_map >= superblock.oldest_map);
+    ceph_assert(service.max_oldest_map >= superblock.oldest_map);
   }
 
   // make sure there is something new, here, before we bother flushing
@@ -7270,12 +8016,14 @@ void OSD::handle_osd_map(MOSDMap *m)
   ObjectStore::Transaction t;
   uint64_t txn_size = 0;
 
+  map<epoch_t,mempool::osdmap::map<int64_t,snap_interval_set_t>> purged_snaps;
+
   // store new maps: queue for disk and put in the osdmap cache
-  epoch_t start = MAX(superblock.newest_map + 1, first);
+  epoch_t start = std::max(superblock.newest_map + 1, first);
   for (epoch_t e = start; e <= last; e++) {
     if (txn_size >= t.get_num_bytes()) {
       derr << __func__ << " transaction size overflowed" << dendl;
-      assert(txn_size < t.get_num_bytes());
+      ceph_assert(txn_size < t.get_num_bytes());
     }
     txn_size = t.get_num_bytes();
     map<epoch_t,bufferlist>::iterator p;
@@ -7287,11 +8035,12 @@ void OSD::handle_osd_map(MOSDMap *m)
 
       o->decode(bl);
 
+      purged_snaps[e] = o->get_new_purged_snaps();
+
       ghobject_t fulloid = get_osdmap_pobject_name(e);
       t.write(coll_t::meta(), fulloid, 0, bl.length(), bl);
-      pin_map_bl(e, bl);
-      pinned_maps.push_back(add_map(o));
-
+      added_maps[e] = add_map(o);
+      added_maps_bl[e] = bl;
       got_full_map(e);
       continue;
     }
@@ -7302,22 +8051,26 @@ void OSD::handle_osd_map(MOSDMap *m)
       bufferlist& bl = p->second;
       ghobject_t oid = get_inc_osdmap_pobject_name(e);
       t.write(coll_t::meta(), oid, 0, bl.length(), bl);
-      pin_map_inc_bl(e, bl);
 
       OSDMap *o = new OSDMap;
       if (e > 1) {
        bufferlist obl;
         bool got = get_map_bl(e - 1, obl);
-        assert(got);
+       if (!got) {
+         auto p = added_maps_bl.find(e - 1);
+         ceph_assert(p != added_maps_bl.end());
+         obl = p->second;
+       }
        o->decode(obl);
       }
 
       OSDMap::Incremental inc;
-      bufferlist::iterator p = bl.begin();
+      auto p = bl.cbegin();
       inc.decode(p);
+
       if (o->apply_incremental(inc) < 0) {
-       derr << "ERROR: bad fsid?  i have " << osdmap->get_fsid() << " and inc has " << inc.fsid << dendl;
-       assert(0 == "bad fsid");
+       derr << "ERROR: bad fsid?  i have " << get_osdmap()->get_fsid() << " and inc has " << inc.fsid << dendl;
+       ceph_abort_msg("bad fsid");
       }
 
       bufferlist fbl;
@@ -7341,18 +8094,26 @@ void OSD::handle_osd_map(MOSDMap *m)
        delete o;
        request_full_map(e, last);
        last = e - 1;
+
+       // don't continue committing if we failed to enc the first inc map
+       if (last < start) {
+         dout(10) << __func__ << " bailing because last < start (" << last << "<" << start << ")" << dendl;
+         m->put();
+         return;
+       }
        break;
       }
       got_full_map(e);
+      purged_snaps[e] = o->get_new_purged_snaps();
 
       ghobject_t fulloid = get_osdmap_pobject_name(e);
       t.write(coll_t::meta(), fulloid, 0, fbl.length(), fbl);
-      pin_map_bl(e, fbl);
-      pinned_maps.push_back(add_map(o));
+      added_maps[e] = add_map(o);
+      added_maps_bl[e] = fbl;
       continue;
     }
 
-    assert(0 == "MOSDMap lied about what maps it had?");
+    ceph_abort_msg("MOSDMap lied about what maps it had?");
   }
 
   // even if this map isn't from a mon, we may have satisfied our subscription
@@ -7364,15 +8125,10 @@ void OSD::handle_osd_map(MOSDMap *m)
     rerequest_full_maps();
   }
 
-  if (last <= superblock.newest_map) {
-    dout(10) << " no new maps here, dropping" << dendl;
-    m->put();
-    return;
-  }
-
   if (superblock.oldest_map) {
     // make sure we at least keep pace with incoming maps
     trim_maps(m->oldest_map, last - first + 1, skip_maps);
+    pg_num_history.prune(superblock.oldest_map);
   }
 
   if (!superblock.oldest_map || skip_maps)
@@ -7380,20 +8136,86 @@ void OSD::handle_osd_map(MOSDMap *m)
   superblock.newest_map = last;
   superblock.current_epoch = last;
 
-  // note in the superblock that we were clean thru the prior epoch
-  epoch_t boot_epoch = service.get_boot_epoch();
-  if (boot_epoch && boot_epoch >= superblock.mounted) {
-    superblock.mounted = boot_epoch;
-    superblock.clean_thru = last;
+  // note in the superblock that we were clean thru the prior epoch
+  epoch_t boot_epoch = service.get_boot_epoch();
+  if (boot_epoch && boot_epoch >= superblock.mounted) {
+    superblock.mounted = boot_epoch;
+    superblock.clean_thru = last;
+  }
+
+  // check for pg_num changes and deleted pools
+  OSDMapRef lastmap;
+  for (auto& i : added_maps) {
+    if (!lastmap) {
+      if (!(lastmap = service.try_get_map(i.first - 1))) {
+        dout(10) << __func__ << " can't get previous map " << i.first - 1
+                 << " probably first start of this osd" << dendl;
+        continue;
+      }
+    }
+    ceph_assert(lastmap->get_epoch() + 1 == i.second->get_epoch());
+    for (auto& j : lastmap->get_pools()) {
+      if (!i.second->have_pg_pool(j.first)) {
+       pg_num_history.log_pool_delete(i.first, j.first);
+       dout(10) << __func__ << " recording final pg_pool_t for pool "
+                << j.first << dendl;
+       // this information is needed by _make_pg() if have to restart before
+       // the pool is deleted and need to instantiate a new (zombie) PG[Pool].
+       ghobject_t obj = make_final_pool_info_oid(j.first);
+       bufferlist bl;
+       encode(j.second, bl, CEPH_FEATURES_ALL);
+       string name = lastmap->get_pool_name(j.first);
+       encode(name, bl);
+       map<string,string> profile;
+       if (lastmap->get_pg_pool(j.first)->is_erasure()) {
+         profile = lastmap->get_erasure_code_profile(
+           lastmap->get_pg_pool(j.first)->erasure_code_profile);
+       }
+       encode(profile, bl);
+       t.write(coll_t::meta(), obj, 0, bl.length(), bl);
+      } else if (unsigned new_pg_num = i.second->get_pg_num(j.first);
+                new_pg_num != j.second.get_pg_num()) {
+       dout(10) << __func__ << " recording pool " << j.first << " pg_num "
+                << j.second.get_pg_num() << " -> " << new_pg_num << dendl;
+       pg_num_history.log_pg_num_change(i.first, j.first, new_pg_num);
+      }
+    }
+    for (auto& j : i.second->get_pools()) {
+      if (!lastmap->have_pg_pool(j.first)) {
+       dout(10) << __func__ << " recording new pool " << j.first << " pg_num "
+                << j.second.get_pg_num() << dendl;
+       pg_num_history.log_pg_num_change(i.first, j.first,
+                                        j.second.get_pg_num());
+      }
+    }
+    lastmap = i.second;
+  }
+  pg_num_history.epoch = last;
+  {
+    bufferlist bl;
+    ::encode(pg_num_history, bl);
+    t.write(coll_t::meta(), make_pg_num_history_oid(), 0, bl.length(), bl);
+    dout(20) << __func__ << " pg_num_history " << pg_num_history << dendl;
+  }
+
+  // record new purged_snaps
+  if (superblock.purged_snaps_last == start - 1) {
+    SnapMapper::record_purged_snaps(cct, store, service.meta_ch,
+                                   make_purged_snaps_oid(), &t,
+                                   purged_snaps);
+    superblock.purged_snaps_last = last;
+  } else {
+    dout(10) << __func__ << " superblock purged_snaps_last is "
+            << superblock.purged_snaps_last
+            << ", not recording new purged_snaps" << dendl;
   }
 
   // superblock and commit
   write_superblock(t);
+  t.register_on_commit(new C_OnMapCommit(this, start, last, m));
   store->queue_transaction(
-    service.meta_osr.get(),
-    std::move(t),
-    new C_OnMapApply(&service, pinned_maps, last),
-    new C_OnMapCommit(this, start, last, m), 0);
+    service.meta_ch,
+    std::move(t));
   service.publish_superblock(superblock);
 }
 
@@ -7404,12 +8226,19 @@ void OSD::_committed_osd_maps(epoch_t first, epoch_t last, MOSDMap *m)
     dout(10) << __func__ << " bailing, we are shutting down" << dendl;
     return;
   }
-  Mutex::Locker l(osd_lock);
-  map_lock.get_write();
+  std::lock_guard l(osd_lock);
+  if (is_stopping()) {
+    dout(10) << __func__ << " bailing, we are shutting down" << dendl;
+    return;
+  }
+  map_lock.lock();
+
+  ceph_assert(first <= last);
 
   bool do_shutdown = false;
   bool do_restart = false;
   bool network_error = false;
+  OSDMapRef osdmap = get_osdmap();
 
   // advance through the new maps
   for (epoch_t cur = first; cur <= last; cur++) {
@@ -7419,14 +8248,15 @@ void OSD::_committed_osd_maps(epoch_t first, epoch_t last, MOSDMap *m)
             << ")" << dendl;
 
     OSDMapRef newmap = get_map(cur);
-    assert(newmap);  // we just cached it above!
+    ceph_assert(newmap);  // we just cached it above!
 
-    // start blacklisting messages sent to peers that go down.
+    // start blocklisting messages sent to peers that go down.
     service.pre_publish_map(newmap);
 
     // kill connections to newly down osds
     bool waited_for_reservations = false;
     set<int> old;
+    osdmap = get_osdmap();
     osdmap->get_all_osds(old);
     for (set<int>::iterator p = old.begin(); p != old.end(); ++p) {
       if (*p != whoami &&
@@ -7444,8 +8274,7 @@ void OSD::_committed_osd_maps(epoch_t first, epoch_t last, MOSDMap *m)
       }
     }
 
-    if (osdmap->test_flag(CEPH_OSDMAP_NOUP) !=
-       newmap->test_flag(CEPH_OSDMAP_NOUP)) {
+    if (osdmap->is_noup(whoami) != newmap->is_noup(whoami)) {
       dout(10) << __func__ << " NOUP flag changed in " << newmap->get_epoch()
               << dendl;
       if (is_booting()) {
@@ -7459,13 +8288,14 @@ void OSD::_committed_osd_maps(epoch_t first, epoch_t last, MOSDMap *m)
       }
     }
 
-    osdmap = newmap;
+    osdmap = std::move(newmap);
+    set_osdmap(osdmap);
     epoch_t up_epoch;
     epoch_t boot_epoch;
     service.retrieve_epochs(&boot_epoch, &up_epoch, NULL);
     if (!up_epoch &&
        osdmap->is_up(whoami) &&
-       osdmap->get_inst(whoami) == client_messenger->get_myinst()) {
+       osdmap->get_addrs(whoami) == client_messenger->get_myaddrs()) {
       up_epoch = osdmap->get_epoch();
       dout(10) << "up_epoch is " << up_epoch << dendl;
       if (!boot_epoch) {
@@ -7476,72 +8306,84 @@ void OSD::_committed_osd_maps(epoch_t first, epoch_t last, MOSDMap *m)
     }
   }
 
-  had_map_since = ceph_clock_now();
-
   epoch_t _bind_epoch = service.get_bind_epoch();
   if (osdmap->is_up(whoami) &&
-      osdmap->get_addr(whoami) == client_messenger->get_myaddr() &&
+      osdmap->get_addrs(whoami).legacy_equals(
+       client_messenger->get_myaddrs()) &&
       _bind_epoch < osdmap->get_up_from(whoami)) {
 
     if (is_booting()) {
       dout(1) << "state: booting -> active" << dendl;
       set_state(STATE_ACTIVE);
+      do_restart = false;
 
       // set incarnation so that osd_reqid_t's we generate for our
       // objecter requests are unique across restarts.
       service.objecter->set_client_incarnation(osdmap->get_epoch());
+      cancel_pending_failures();
     }
   }
 
   if (osdmap->get_epoch() > 0 &&
       is_active()) {
     if (!osdmap->exists(whoami)) {
-      dout(0) << "map says i do not exist.  shutting down." << dendl;
+      derr << "map says i do not exist.  shutting down." << dendl;
       do_shutdown = true;   // don't call shutdown() while we have
                            // everything paused
+    } else if (osdmap->is_stop(whoami)) {
+      derr << "map says i am stopped by admin. shutting down." << dendl;
+      do_shutdown = true;
     } else if (!osdmap->is_up(whoami) ||
-              !osdmap->get_addr(whoami).probably_equals(
-                client_messenger->get_myaddr()) ||
-              !osdmap->get_cluster_addr(whoami).probably_equals(
-                cluster_messenger->get_myaddr()) ||
-              !osdmap->get_hb_back_addr(whoami).probably_equals(
-                hb_back_server_messenger->get_myaddr()) ||
-              (osdmap->get_hb_front_addr(whoami) != entity_addr_t() &&
-                !osdmap->get_hb_front_addr(whoami).probably_equals(
-                 hb_front_server_messenger->get_myaddr()))) {
+              !osdmap->get_addrs(whoami).legacy_equals(
+                client_messenger->get_myaddrs()) ||
+              !osdmap->get_cluster_addrs(whoami).legacy_equals(
+                cluster_messenger->get_myaddrs()) ||
+              !osdmap->get_hb_back_addrs(whoami).legacy_equals(
+                hb_back_server_messenger->get_myaddrs()) ||
+              !osdmap->get_hb_front_addrs(whoami).legacy_equals(
+                hb_front_server_messenger->get_myaddrs())) {
       if (!osdmap->is_up(whoami)) {
        if (service.is_preparing_to_stop() || service.is_stopping()) {
          service.got_stop_ack();
        } else {
-         clog->warn() << "map e" << osdmap->get_epoch()
-                      << " wrongly marked me down at e"
-                      << osdmap->get_down_at(whoami);
+          clog->warn() << "Monitor daemon marked osd." << whoami << " down, "
+                          "but it is still running";
+          clog->debug() << "map e" << osdmap->get_epoch()
+                        << " wrongly marked me down at e"
+                        << osdmap->get_down_at(whoami);
+       }
+       if (monc->monmap.min_mon_release >= ceph_release_t::octopus) {
+         // note that this is best-effort...
+         monc->send_mon_message(
+           new MOSDMarkMeDead(
+             monc->get_fsid(),
+             whoami,
+             osdmap->get_epoch()));
        }
-      } else if (!osdmap->get_addr(whoami).probably_equals(
-                  client_messenger->get_myaddr())) {
+      } else if (!osdmap->get_addrs(whoami).legacy_equals(
+                  client_messenger->get_myaddrs())) {
        clog->error() << "map e" << osdmap->get_epoch()
-                     << " had wrong client addr (" << osdmap->get_addr(whoami)
-                     << " != my " << client_messenger->get_myaddr() << ")";
-      } else if (!osdmap->get_cluster_addr(whoami).probably_equals(
-                  cluster_messenger->get_myaddr())) {
+                     << " had wrong client addr (" << osdmap->get_addrs(whoami)
+                     << " != my " << client_messenger->get_myaddrs() << ")";
+      } else if (!osdmap->get_cluster_addrs(whoami).legacy_equals(
+                  cluster_messenger->get_myaddrs())) {
        clog->error() << "map e" << osdmap->get_epoch()
                      << " had wrong cluster addr ("
-                     << osdmap->get_cluster_addr(whoami)
-                     << " != my " << cluster_messenger->get_myaddr() << ")";
-      } else if (!osdmap->get_hb_back_addr(whoami).probably_equals(
-                  hb_back_server_messenger->get_myaddr())) {
+                     << osdmap->get_cluster_addrs(whoami)
+                     << " != my " << cluster_messenger->get_myaddrs() << ")";
+      } else if (!osdmap->get_hb_back_addrs(whoami).legacy_equals(
+                  hb_back_server_messenger->get_myaddrs())) {
        clog->error() << "map e" << osdmap->get_epoch()
-                     << " had wrong hb back addr ("
-                     << osdmap->get_hb_back_addr(whoami)
-                     << " != my " << hb_back_server_messenger->get_myaddr()
+                     << " had wrong heartbeat back addr ("
+                     << osdmap->get_hb_back_addrs(whoami)
+                     << " != my " << hb_back_server_messenger->get_myaddrs()
                      << ")";
-      } else if (osdmap->get_hb_front_addr(whoami) != entity_addr_t() &&
-                !osdmap->get_hb_front_addr(whoami).probably_equals(
-                  hb_front_server_messenger->get_myaddr())) {
+      } else if (!osdmap->get_hb_front_addrs(whoami).legacy_equals(
+                  hb_front_server_messenger->get_myaddrs())) {
        clog->error() << "map e" << osdmap->get_epoch()
-                     << " had wrong hb front addr ("
-                     << osdmap->get_hb_front_addr(whoami)
-                     << " != my " << hb_front_server_messenger->get_myaddr()
+                     << " had wrong heartbeat front addr ("
+                     << osdmap->get_hb_front_addrs(whoami)
+                     << " != my " << hb_front_server_messenger->get_myaddrs()
                      << ")";
       }
 
@@ -7555,17 +8397,13 @@ void OSD::_committed_osd_maps(epoch_t first, epoch_t last, MOSDMap *m)
        utime_t now = ceph_clock_now();
        utime_t grace = utime_t(cct->_conf->osd_max_markdown_period, 0);
        osd_markdown_log.push_back(now);
-       //clear all out-of-date log
-       while (!osd_markdown_log.empty() &&
-              osd_markdown_log.front() + grace < now)
-         osd_markdown_log.pop_front();
        if ((int)osd_markdown_log.size() > cct->_conf->osd_max_markdown_count) {
-         dout(0) << __func__ << " marked down "
-                 << osd_markdown_log.size()
-                 << " > osd_max_markdown_count "
-                 << cct->_conf->osd_max_markdown_count
-                 << " in last " << grace << " seconds, shutting down"
-                 << dendl;
+         derr << __func__ << " marked down "
+              << osd_markdown_log.size()
+              << " > osd_max_markdown_count "
+              << cct->_conf->osd_max_markdown_count
+              << " in last " << grace << " seconds, shutting down"
+              << dendl;
          do_restart = false;
          do_shutdown = true;
        }
@@ -7575,49 +8413,33 @@ void OSD::_committed_osd_maps(epoch_t first, epoch_t last, MOSDMap *m)
        set<int> avoid_ports;
 #if defined(__FreeBSD__)
         // prevent FreeBSD from grabbing the client_messenger port during
-        // rebinding. In which case a cluster_meesneger will connect also 
+        // rebinding. In which case a cluster_meesneger will connect also
        // to the same port
-       avoid_ports.insert(client_messenger->get_myaddr().get_port());
+       client_messenger->get_myaddrs().get_ports(&avoid_ports);
 #endif
-       avoid_ports.insert(cluster_messenger->get_myaddr().get_port());
-       avoid_ports.insert(hb_back_server_messenger->get_myaddr().get_port());
-       avoid_ports.insert(hb_front_server_messenger->get_myaddr().get_port());
+       cluster_messenger->get_myaddrs().get_ports(&avoid_ports);
 
        int r = cluster_messenger->rebind(avoid_ports);
        if (r != 0) {
          do_shutdown = true;  // FIXME: do_restart?
           network_error = true;
-          dout(0) << __func__ << " marked down:"
-                  << " rebind cluster_messenger failed" << dendl;
-        }
-
-       r = hb_back_server_messenger->rebind(avoid_ports);
-       if (r != 0) {
-         do_shutdown = true;  // FIXME: do_restart?
-          network_error = true;
-          dout(0) << __func__ << " marked down:"
-                  << " rebind hb_back_server_messenger failed" << dendl;
-        }
-
-       r = hb_front_server_messenger->rebind(avoid_ports);
-       if (r != 0) {
-         do_shutdown = true;  // FIXME: do_restart?
-          network_error = true;
-          dout(0) << __func__ << " marked down:" 
-                  << " rebind hb_front_server_messenger failed" << dendl;
+          derr << __func__ << " marked down:"
+              << " rebind cluster_messenger failed" << dendl;
         }
 
+       hb_back_server_messenger->mark_down_all();
+       hb_front_server_messenger->mark_down_all();
        hb_front_client_messenger->mark_down_all();
        hb_back_client_messenger->mark_down_all();
 
-       reset_heartbeat_peers();
+       reset_heartbeat_peers(true);
       }
     }
   }
 
-  map_lock.put_write();
+  map_lock.unlock();
 
-  check_osdmap_features(store);
+  check_osdmap_features();
 
   // yay!
   consume_map();
@@ -7625,34 +8447,23 @@ void OSD::_committed_osd_maps(epoch_t first, epoch_t last, MOSDMap *m)
   if (is_active() || is_waiting_for_healthy())
     maybe_update_heartbeat_peers();
 
-  if (!is_active()) {
-    dout(10) << " not yet active; waiting for peering wq to drain" << dendl;
-    peering_wq.drain();
-  } else {
+  if (is_active()) {
     activate_map();
   }
 
-  if (m->newest_map && m->newest_map > last) {
-    dout(10) << " msg say newest map is " << m->newest_map
-            << ", requesting more" << dendl;
-    osdmap_subscribe(osdmap->get_epoch()+1, false);
-  }
-  else if (do_shutdown) {
+  if (do_shutdown) {
     if (network_error) {
-      Mutex::Locker l(heartbeat_lock);
-      map<int,pair<utime_t,entity_inst_t>>::iterator it =
-       failure_pending.begin();
-      while (it != failure_pending.end()) {
-        dout(10) << "handle_osd_ping canceling in-flight failure report for osd."
-                << it->first << dendl;
-        send_still_alive(osdmap->get_epoch(), it->second.second);
-        failure_pending.erase(it++);
-      }
+      cancel_pending_failures();
     }
     // trigger shutdown in a different thread
     dout(0) << __func__ << " shutdown OSD via async signal" << dendl;
     queue_async_signal(SIGINT);
   }
+  else if (m->newest_map && m->newest_map > last) {
+    dout(10) << " msg say newest map is " << m->newest_map
+            << ", requesting more" << dendl;
+    osdmap_subscribe(osdmap->get_epoch()+1, false);
+  }
   else if (is_preboot()) {
     if (m->get_source().is_mon())
       _preboot(m->oldest_map, m->newest_map);
@@ -7664,7 +8475,7 @@ void OSD::_committed_osd_maps(epoch_t first, epoch_t last, MOSDMap *m)
 
 }
 
-void OSD::check_osdmap_features(ObjectStore *fs)
+void OSD::check_osdmap_features()
 {
   // adjust required feature bits?
 
@@ -7675,6 +8486,7 @@ void OSD::check_osdmap_features(ObjectStore *fs)
   // current memory location, and setting or clearing bits in integer
   // fields, and we are the only writer, this is not a problem.
 
+  const auto osdmap = get_osdmap();
   {
     Messenger::Policy p = client_messenger->get_default_policy();
     uint64_t mask;
@@ -7710,164 +8522,398 @@ void OSD::check_osdmap_features(ObjectStore *fs)
       cluster_messenger->set_policy(entity_name_t::TYPE_OSD, p);
     }
 
-    if ((features & CEPH_FEATURE_OSD_ERASURE_CODES) &&
-       !superblock.compat_features.incompat.contains(CEPH_OSD_FEATURE_INCOMPAT_SHARDS)) {
+    if (!superblock.compat_features.incompat.contains(CEPH_OSD_FEATURE_INCOMPAT_SHARDS)) {
       dout(0) << __func__ << " enabling on-disk ERASURE CODES compat feature" << dendl;
       superblock.compat_features.incompat.insert(CEPH_OSD_FEATURE_INCOMPAT_SHARDS);
       ObjectStore::Transaction t;
       write_superblock(t);
-      int err = store->queue_transaction(service.meta_osr.get(), std::move(t), NULL);
-      assert(err == 0);
+      int err = store->queue_transaction(service.meta_ch, std::move(t), NULL);
+      ceph_assert(err == 0);
     }
   }
+
+  if (osdmap->require_osd_release < ceph_release_t::nautilus) {
+    hb_front_server_messenger->set_require_authorizer(false);
+    hb_back_server_messenger->set_require_authorizer(false);
+  } else {
+    hb_front_server_messenger->set_require_authorizer(true);
+    hb_back_server_messenger->set_require_authorizer(true);
+  }
+
+  if (osdmap->require_osd_release != last_require_osd_release) {
+    dout(1) << __func__ << " require_osd_release " << last_require_osd_release
+           << " -> " << to_string(osdmap->require_osd_release) << dendl;
+    store->write_meta("require_osd_release",
+                     stringify((int)osdmap->require_osd_release));
+    last_require_osd_release = osdmap->require_osd_release;
+  }
+}
+
+struct C_FinishSplits : public Context {
+  OSD *osd;
+  set<PGRef> pgs;
+  C_FinishSplits(OSD *osd, const set<PGRef> &in)
+    : osd(osd), pgs(in) {}
+  void finish(int r) override {
+    osd->_finish_splits(pgs);
+  }
+};
+
+void OSD::_finish_splits(set<PGRef>& pgs)
+{
+  dout(10) << __func__ << " " << pgs << dendl;
+  if (is_stopping())
+    return;
+  for (set<PGRef>::iterator i = pgs.begin();
+       i != pgs.end();
+       ++i) {
+    PG *pg = i->get();
+
+    PeeringCtx rctx = create_context();
+    pg->lock();
+    dout(10) << __func__ << " " << *pg << dendl;
+    epoch_t e = pg->get_osdmap_epoch();
+    pg->handle_initialize(rctx);
+    pg->queue_null(e, e);
+    dispatch_context(rctx, pg, service.get_osdmap());
+    pg->unlock();
+
+    unsigned shard_index = pg->pg_id.hash_to_shard(num_shards);
+    shards[shard_index]->register_and_wake_split_child(pg);
+  }
+};
+
+bool OSD::add_merge_waiter(OSDMapRef nextmap, spg_t target, PGRef src,
+                          unsigned need)
+{
+  std::lock_guard l(merge_lock);
+  auto& p = merge_waiters[nextmap->get_epoch()][target];
+  p[src->pg_id] = src;
+  dout(10) << __func__ << " added merge_waiter " << src->pg_id
+          << " for " << target  << ", have " << p.size() << "/" << need
+          << dendl;
+  return p.size() == need;
 }
 
 bool OSD::advance_pg(
-  epoch_t osd_epoch, PG *pg,
+  epoch_t osd_epoch,
+  PG *pg,
   ThreadPool::TPHandle &handle,
-  PG::RecoveryCtx *rctx,
-  set<boost::intrusive_ptr<PG> > *new_pgs)
+  PeeringCtx &rctx)
 {
-  assert(pg->is_locked());
-  epoch_t next_epoch = pg->get_osdmap()->get_epoch() + 1;
-  OSDMapRef lastmap = pg->get_osdmap();
-
-  if (lastmap->get_epoch() == osd_epoch)
+  if (osd_epoch <= pg->get_osdmap_epoch()) {
     return true;
-  assert(lastmap->get_epoch() < osd_epoch);
-
-  epoch_t min_epoch = service.get_min_pg_epoch();
-  epoch_t max;
-  if (min_epoch) {
-    max = min_epoch + cct->_conf->osd_map_max_advance;
-  } else {
-    max = next_epoch + cct->_conf->osd_map_max_advance;
   }
+  ceph_assert(pg->is_locked());
+  OSDMapRef lastmap = pg->get_osdmap();
+  set<PGRef> new_pgs;  // any split children
+  bool ret = true;
 
-  for (;
-       next_epoch <= osd_epoch && next_epoch <= max;
+  unsigned old_pg_num = lastmap->have_pg_pool(pg->pg_id.pool()) ?
+    lastmap->get_pg_num(pg->pg_id.pool()) : 0;
+  for (epoch_t next_epoch = pg->get_osdmap_epoch() + 1;
+       next_epoch <= osd_epoch;
        ++next_epoch) {
     OSDMapRef nextmap = service.try_get_map(next_epoch);
     if (!nextmap) {
       dout(20) << __func__ << " missing map " << next_epoch << dendl;
-      // make sure max is bumped up so that we can get past any
-      // gap in maps
-      max = MAX(max, next_epoch + cct->_conf->osd_map_max_advance);
       continue;
     }
 
+    unsigned new_pg_num =
+      (old_pg_num && nextmap->have_pg_pool(pg->pg_id.pool())) ?
+      nextmap->get_pg_num(pg->pg_id.pool()) : 0;
+    if (old_pg_num && new_pg_num && old_pg_num != new_pg_num) {
+      // check for merge
+      if (nextmap->have_pg_pool(pg->pg_id.pool())) {
+       spg_t parent;
+       if (pg->pg_id.is_merge_source(
+             old_pg_num,
+             new_pg_num,
+             &parent)) {
+         // we are merge source
+         PGRef spg = pg; // carry a ref
+         dout(1) << __func__ << " " << pg->pg_id
+                 << " is merge source, target is " << parent
+                  << dendl;
+         pg->write_if_dirty(rctx);
+         if (!new_pgs.empty()) {
+           rctx.transaction.register_on_applied(new C_FinishSplits(this,
+                                                                   new_pgs));
+           new_pgs.clear();
+         }
+         dispatch_context(rctx, pg, pg->get_osdmap(), &handle);
+         pg->ch->flush();
+         // release backoffs explicitly, since the on_shutdown path
+         // aggressively tears down backoff state.
+         if (pg->is_primary()) {
+           pg->release_pg_backoffs();
+         }
+         pg->on_shutdown();
+         OSDShard *sdata = pg->osd_shard;
+         {
+           std::lock_guard l(sdata->shard_lock);
+           if (pg->pg_slot) {
+             sdata->_detach_pg(pg->pg_slot);
+             // update pg count now since we might not get an osdmap
+             // any time soon.
+             if (pg->is_primary())
+               logger->dec(l_osd_pg_primary);
+             else if (pg->is_nonprimary())
+               logger->dec(l_osd_pg_replica); // misnomer
+             else
+               logger->dec(l_osd_pg_stray);
+           }
+         }
+         pg->unlock();
+
+         set<spg_t> children;
+         parent.is_split(new_pg_num, old_pg_num, &children);
+         if (add_merge_waiter(nextmap, parent, pg, children.size())) {
+           enqueue_peering_evt(
+             parent,
+             PGPeeringEventRef(
+               std::make_shared<PGPeeringEvent>(
+                 nextmap->get_epoch(),
+                 nextmap->get_epoch(),
+                 NullEvt())));
+         }
+         ret = false;
+         goto out;
+       } else if (pg->pg_id.is_merge_target(old_pg_num, new_pg_num)) {
+         // we are merge target
+         set<spg_t> children;
+         pg->pg_id.is_split(new_pg_num, old_pg_num, &children);
+         dout(20) << __func__ << " " << pg->pg_id
+                  << " is merge target, sources are " << children
+                  << dendl;
+         map<spg_t,PGRef> sources;
+         {
+           std::lock_guard l(merge_lock);
+           auto& s = merge_waiters[nextmap->get_epoch()][pg->pg_id];
+           unsigned need = children.size();
+           dout(20) << __func__ << " have " << s.size() << "/"
+                    << need << dendl;
+           if (s.size() == need) {
+             sources.swap(s);
+             merge_waiters[nextmap->get_epoch()].erase(pg->pg_id);
+             if (merge_waiters[nextmap->get_epoch()].empty()) {
+               merge_waiters.erase(nextmap->get_epoch());
+             }
+           }
+         }
+         if (!sources.empty()) {
+           unsigned new_pg_num = nextmap->get_pg_num(pg->pg_id.pool());
+           unsigned split_bits = pg->pg_id.get_split_bits(new_pg_num);
+           dout(1) << __func__ << " merging " << pg->pg_id << dendl;
+           pg->merge_from(
+             sources, rctx, split_bits,
+             nextmap->get_pg_pool(
+               pg->pg_id.pool())->last_pg_merge_meta);
+           pg->pg_slot->waiting_for_merge_epoch = 0;
+         } else {
+           dout(20) << __func__ << " not ready to merge yet" << dendl;
+           pg->write_if_dirty(rctx);
+           if (!new_pgs.empty()) {
+             rctx.transaction.register_on_applied(new C_FinishSplits(this,
+                                                                     new_pgs));
+             new_pgs.clear();
+           }
+           dispatch_context(rctx, pg, pg->get_osdmap(), &handle);
+           pg->unlock();
+           // kick source(s) to get them ready
+           for (auto& i : children) {
+             dout(20) << __func__ << " kicking source " << i << dendl;
+             enqueue_peering_evt(
+               i,
+               PGPeeringEventRef(
+                 std::make_shared<PGPeeringEvent>(
+                   nextmap->get_epoch(),
+                   nextmap->get_epoch(),
+                   NullEvt())));
+           }
+           ret = false;
+           goto out;
+         }
+       }
+      }
+    }
+
     vector<int> newup, newacting;
     int up_primary, acting_primary;
     nextmap->pg_to_up_acting_osds(
-      pg->info.pgid.pgid,
+      pg->pg_id.pgid,
       &newup, &up_primary,
       &newacting, &acting_primary);
     pg->handle_advance_map(
       nextmap, lastmap, newup, up_primary,
       newacting, acting_primary, rctx);
 
-    // Check for split!
-    set<spg_t> children;
-    spg_t parent(pg->info.pgid);
-    if (parent.is_split(
-       lastmap->get_pg_num(pg->pool.id),
-       nextmap->get_pg_num(pg->pool.id),
-       &children)) {
-      service.mark_split_in_progress(pg->info.pgid, children);
-      split_pgs(
-       pg, children, new_pgs, lastmap, nextmap,
-       rctx);
+    auto oldpool = lastmap->get_pools().find(pg->pg_id.pool());
+    auto newpool = nextmap->get_pools().find(pg->pg_id.pool());
+    if (oldpool != lastmap->get_pools().end()
+        && newpool != nextmap->get_pools().end()) {
+      dout(20) << __func__
+              << " new pool opts " << newpool->second.opts
+              << " old pool opts " << oldpool->second.opts
+              << dendl;
+
+      double old_min_interval = 0, new_min_interval = 0;
+      oldpool->second.opts.get(pool_opts_t::SCRUB_MIN_INTERVAL, &old_min_interval);
+      newpool->second.opts.get(pool_opts_t::SCRUB_MIN_INTERVAL, &new_min_interval);
+
+      double old_max_interval = 0, new_max_interval = 0;
+      oldpool->second.opts.get(pool_opts_t::SCRUB_MAX_INTERVAL, &old_max_interval);
+      newpool->second.opts.get(pool_opts_t::SCRUB_MAX_INTERVAL, &new_max_interval);
+
+      // Assume if an interval is change from set to unset or vice versa the actual config
+      // is different.  Keep it simple even if it is possible to call resched_all_scrub()
+      // unnecessarily.
+      if (old_min_interval != new_min_interval || old_max_interval != new_max_interval) {
+       pg->on_info_history_change();
+      }
+    }
+
+    if (new_pg_num && old_pg_num != new_pg_num) {
+      // check for split
+      set<spg_t> children;
+      if (pg->pg_id.is_split(
+           old_pg_num,
+           new_pg_num,
+           &children)) {
+       split_pgs(
+         pg, children, &new_pgs, lastmap, nextmap,
+         rctx);
+      }
     }
 
     lastmap = nextmap;
+    old_pg_num = new_pg_num;
     handle.reset_tp_timeout();
   }
-  service.pg_update_epoch(pg->info.pgid, lastmap->get_epoch());
   pg->handle_activate_map(rctx);
-  if (next_epoch <= osd_epoch) {
-    dout(10) << __func__ << " advanced to max " << max
-            << " past min epoch " << min_epoch
-            << " ... will requeue " << *pg << dendl;
-    return false;
+
+  ret = true;
+ out:
+  if (!new_pgs.empty()) {
+    rctx.transaction.register_on_applied(new C_FinishSplits(this, new_pgs));
   }
-  return true;
+  return ret;
 }
 
 void OSD::consume_map()
 {
-  assert(osd_lock.is_locked());
+  ceph_assert(ceph_mutex_is_locked(osd_lock));
+  auto osdmap = get_osdmap();
   dout(7) << "consume_map version " << osdmap->get_epoch() << dendl;
 
-  int num_pg_primary = 0, num_pg_replica = 0, num_pg_stray = 0;
-  list<PGRef> to_remove;
-
-  // scan pg's
-  {
-    RWLock::RLocker l(pg_map_lock);
-    for (ceph::unordered_map<spg_t,PG*>::iterator it = pg_map.begin();
-        it != pg_map.end();
-        ++it) {
-      PG *pg = it->second;
-      pg->lock();
-      if (pg->is_primary())
-        num_pg_primary++;
-      else if (pg->is_replica())
-        num_pg_replica++;
-      else
-        num_pg_stray++;
+  /** make sure the cluster is speaking in SORTBITWISE, because we don't
+   *  speak the older sorting version any more. Be careful not to force
+   *  a shutdown if we are merely processing old maps, though.
+   */
+  if (!osdmap->test_flag(CEPH_OSDMAP_SORTBITWISE) && is_active()) {
+    derr << __func__ << " SORTBITWISE flag is not set" << dendl;
+    ceph_abort();
+  }
 
-      if (!osdmap->have_pg_pool(pg->info.pgid.pool())) {
-        //pool is deleted!
-        to_remove.push_back(PGRef(pg));
-      } else {
-        service.init_splits_between(it->first, service.get_osdmap(), osdmap);
-      }
+  service.pre_publish_map(osdmap);
+  service.await_reserved_maps();
+  service.publish_map(osdmap);
 
-      pg->unlock();
+  // prime splits and merges
+  set<pair<spg_t,epoch_t>> newly_split;  // splits, and when
+  set<pair<spg_t,epoch_t>> merge_pgs;    // merge participants, and when
+  for (auto& shard : shards) {
+    shard->identify_splits_and_merges(osdmap, &newly_split, &merge_pgs);
+  }
+  if (!newly_split.empty()) {
+    for (auto& shard : shards) {
+      shard->prime_splits(osdmap, &newly_split);
     }
+    ceph_assert(newly_split.empty());
   }
 
-  for (list<PGRef>::iterator i = to_remove.begin();
-       i != to_remove.end();
-       to_remove.erase(i++)) {
-    RWLock::WLocker locker(pg_map_lock);
-    (*i)->lock();
-    _remove_pg(&**i);
-    (*i)->unlock();
+  // prune sent_ready_to_merge
+  service.prune_sent_ready_to_merge(osdmap);
+
+  // FIXME, maybe: We could race against an incoming peering message
+  // that instantiates a merge PG after identify_merges() below and
+  // never set up its peer to complete the merge.  An OSD restart
+  // would clear it up.  This is a hard race to resolve,
+  // extraordinarily rare (we only merge PGs that are stable and
+  // clean, so it'd have to be an imported PG to an OSD with a
+  // slightly stale OSDMap...), so I'm ignoring it for now.  We plan to
+  // replace all of this with a seastar-based code soon anyway.
+  if (!merge_pgs.empty()) {
+    // mark the pgs we already have, or create new and empty merge
+    // participants for those we are missing.  do this all under the
+    // shard lock so we don't have to worry about racing pg creates
+    // via _process.
+    for (auto& shard : shards) {
+      shard->prime_merges(osdmap, &merge_pgs);
+    }
+    ceph_assert(merge_pgs.empty());
   }
 
-  service.expand_pg_num(service.get_osdmap(), osdmap);
+  service.prune_pg_created();
 
-  service.pre_publish_map(osdmap);
-  service.await_reserved_maps();
-  service.publish_map(osdmap);
+  unsigned pushes_to_free = 0;
+  for (auto& shard : shards) {
+    shard->consume_map(osdmap, &pushes_to_free);
+  }
 
-  service.maybe_inject_dispatch_delay();
+  vector<spg_t> pgids;
+  _get_pgids(&pgids);
 
-  dispatch_sessions_waiting_on_map();
+  // count (FIXME, probably during seastar rewrite)
+  int num_pg_primary = 0, num_pg_replica = 0, num_pg_stray = 0;
+  vector<PGRef> pgs;
+  _get_pgs(&pgs);
+  for (auto& pg : pgs) {
+    // FIXME (probably during seastar rewrite): this is lockless and
+    // racy, but we don't want to take pg lock here.
+    if (pg->is_primary())
+      num_pg_primary++;
+    else if (pg->is_nonprimary())
+      num_pg_replica++;  // misnomer
+    else
+      num_pg_stray++;
+  }
+
+  {
+    // FIXME (as part of seastar rewrite): move to OSDShard
+    std::lock_guard l(pending_creates_lock);
+    for (auto pg = pending_creates_from_osd.begin();
+        pg != pending_creates_from_osd.end();) {
+      if (osdmap->get_pg_acting_role(pg->first, whoami) < 0) {
+       dout(10) << __func__ << " pg " << pg->first << " doesn't map here, "
+                << "discarding pending_create_from_osd" << dendl;
+       pg = pending_creates_from_osd.erase(pg);
+      } else {
+       ++pg;
+      }
+    }
+  }
 
   service.maybe_inject_dispatch_delay();
 
-  // remove any PGs which we no longer host from the session waiting_for_pg lists
-  dout(20) << __func__ << " checking waiting_for_pg" << dendl;
-  op_shardedwq.prune_pg_waiters(osdmap, whoami);
+  dispatch_sessions_waiting_on_map();
 
   service.maybe_inject_dispatch_delay();
 
-  // scan pg's
-  {
-    RWLock::RLocker l(pg_map_lock);
-    for (ceph::unordered_map<spg_t,PG*>::iterator it = pg_map.begin();
-        it != pg_map.end();
-        ++it) {
-      PG *pg = it->second;
-      pg->lock();
-      pg->queue_null(osdmap->get_epoch(), osdmap->get_epoch());
-      pg->unlock();
-    }
+  service.release_reserved_pushes(pushes_to_free);
 
-    logger->set(l_osd_pg, pg_map.size());
+  // queue null events to push maps down to individual PGs
+  for (auto pgid : pgids) {
+    enqueue_peering_evt(
+      pgid,
+      PGPeeringEventRef(
+       std::make_shared<PGPeeringEvent>(
+         osdmap->get_epoch(),
+         osdmap->get_epoch(),
+         NullEvt())));
   }
+  logger->set(l_osd_pg, pgids.size());
   logger->set(l_osd_pg_primary, num_pg_primary);
   logger->set(l_osd_pg_replica, num_pg_replica);
   logger->set(l_osd_pg_stray, num_pg_stray);
@@ -7875,20 +8921,11 @@ void OSD::consume_map()
 
 void OSD::activate_map()
 {
-  assert(osd_lock.is_locked());
+  ceph_assert(ceph_mutex_is_locked(osd_lock));
+  auto osdmap = get_osdmap();
 
   dout(7) << "activate_map version " << osdmap->get_epoch() << dendl;
 
-  if (!osdmap->test_flag(CEPH_OSDMAP_SORTBITWISE)) {
-    derr << __func__ << " SORTBITWISE flag is not set" << dendl;
-    ceph_abort();
-  }
-
-  if (osdmap->test_flag(CEPH_OSDMAP_FULL)) {
-    dout(10) << " osdmap flagged full, doing onetime osdmap subscribe" << dendl;
-    osdmap_subscribe(osdmap->get_epoch() + 1, false);
-  }
-
   // norecover?
   if (osdmap->test_flag(CEPH_OSDMAP_NORECOVER)) {
     if (!service.recovery_is_paused()) {
@@ -7958,29 +8995,29 @@ bool OSD::require_self_aliveness(const Message *m, epoch_t epoch)
   return true;
 }
 
-bool OSD::require_same_peer_instance(const Message *m, OSDMapRef& map,
+bool OSD::require_same_peer_instance(const Message *m, const OSDMapRef& map,
                                     bool is_fast_dispatch)
 {
   int from = m->get_source().num();
 
   if (map->is_down(from) ||
-      (map->get_cluster_addr(from) != m->get_source_inst().addr)) {
+      (map->get_cluster_addrs(from) != m->get_source_addrs())) {
     dout(5) << "from dead osd." << from << ", marking down, "
            << " msg was " << m->get_source_inst().addr
-           << " expected " << (map->is_up(from) ?
-                               map->get_cluster_addr(from) : entity_addr_t())
+           << " expected "
+           << (map->is_up(from) ?
+               map->get_cluster_addrs(from) : entity_addrvec_t())
            << dendl;
     ConnectionRef con = m->get_connection();
     con->mark_down();
-    Session *s = static_cast<Session*>(con->get_priv());
-    if (s) {
+    if (auto s = ceph::ref_cast<Session>(con->get_priv()); s) {
       if (!is_fast_dispatch)
-       s->session_dispatch_lock.Lock();
+       s->session_dispatch_lock.lock();
       clear_session_waiting_on_map(s);
-      con->set_priv(NULL);   // break ref <-> session cycle, if any
+      con->set_priv(nullptr);   // break ref <-> session cycle, if any
+      s->con.reset();
       if (!is_fast_dispatch)
-       s->session_dispatch_lock.Unlock();
-      s->put();
+       s->session_dispatch_lock.unlock();
     }
     return false;
   }
@@ -7996,10 +9033,11 @@ bool OSD::require_same_or_newer_map(OpRequestRef& op, epoch_t epoch,
                                    bool is_fast_dispatch)
 {
   const Message *m = op->get_req();
+  const auto osdmap = get_osdmap();
   dout(15) << "require_same_or_newer_map " << epoch
           << " (i am " << osdmap->get_epoch() << ") " << m << dendl;
 
-  assert(osd_lock.is_locked());
+  ceph_assert(ceph_mutex_is_locked(osd_lock));
 
   // do they have a newer map?
   if (epoch > osdmap->get_epoch()) {
@@ -8031,55 +9069,56 @@ bool OSD::require_same_or_newer_map(OpRequestRef& op, epoch_t epoch,
 
 void OSD::split_pgs(
   PG *parent,
-  const set<spg_t> &childpgids, set<boost::intrusive_ptr<PG> > *out_pgs,
+  const set<spg_t> &childpgids, set<PGRef> *out_pgs,
   OSDMapRef curmap,
   OSDMapRef nextmap,
-  PG::RecoveryCtx *rctx)
+  PeeringCtx &rctx)
 {
-  unsigned pg_num = nextmap->get_pg_num(
-    parent->pool.id);
-  parent->update_snap_mapper_bits(
-    parent->info.pgid.get_split_bits(pg_num)
-    );
+  unsigned pg_num = nextmap->get_pg_num(parent->pg_id.pool());
+  parent->update_snap_mapper_bits(parent->get_pgid().get_split_bits(pg_num));
 
-  vector<object_stat_sum_t> updated_stats(childpgids.size() + 1);
-  parent->info.stats.stats.sum.split(updated_stats);
+  vector<object_stat_sum_t> updated_stats;
+  parent->start_split_stats(childpgids, &updated_stats);
 
   vector<object_stat_sum_t>::iterator stat_iter = updated_stats.begin();
   for (set<spg_t>::const_iterator i = childpgids.begin();
        i != childpgids.end();
        ++i, ++stat_iter) {
-    assert(stat_iter != updated_stats.end());
-    dout(10) << "Splitting " << *parent << " into " << *i << dendl;
-    assert(service.splitting(*i));
+    ceph_assert(stat_iter != updated_stats.end());
+    dout(10) << __func__ << " splitting " << *parent << " into " << *i << dendl;
     PG* child = _make_pg(nextmap, *i);
     child->lock(true);
     out_pgs->insert(child);
-    rctx->created_pgs.insert(child);
+    child->ch = store->create_new_collection(child->coll);
 
-    unsigned split_bits = i->get_split_bits(pg_num);
-    dout(10) << "pg_num is " << pg_num << dendl;
-    dout(10) << "m_seed " << i->ps() << dendl;
-    dout(10) << "split_bits is " << split_bits << dendl;
+    {
+      uint32_t shard_index = i->hash_to_shard(shards.size());
+      assert(NULL != shards[shard_index]);
+      store->set_collection_commit_queue(child->coll, &(shards[shard_index]->context_queue));
+    }
 
+    unsigned split_bits = i->get_split_bits(pg_num);
+    dout(10) << " pg_num is " << pg_num
+            << ", m_seed " << i->ps()
+            << ", split_bits is " << split_bits << dendl;
     parent->split_colls(
       *i,
       split_bits,
       i->ps(),
-      &child->pool.info,
-      rctx->transaction);
+      &child->get_pool().info,
+      rctx.transaction);
     parent->split_into(
       i->pgid,
       child,
       split_bits);
-    child->info.stats.stats.sum = *stat_iter;
 
-    child->write_if_dirty(*(rctx->transaction));
+    child->init_collection_pool_opts();
+
+    child->finish_split_stats(*stat_iter, rctx.transaction);
     child->unlock();
   }
-  assert(stat_iter != updated_stats.end());
-  parent->info.stats.stats.sum = *stat_iter;
-  parent->write_if_dirty(*(rctx->transaction));
+  ceph_assert(stat_iter != updated_stats.end());
+  parent->finish_split_stats(*stat_iter, rctx.transaction);
 }
 
 /*
@@ -8087,8 +9126,11 @@ void OSD::split_pgs(
  */
 void OSD::handle_pg_create(OpRequestRef op)
 {
-  const MOSDPGCreate *m = static_cast<const MOSDPGCreate*>(op->get_req());
-  assert(m->get_type() == MSG_OSD_PG_CREATE);
+  // NOTE: this can be removed in P release (mimic is the last version to
+  // send MOSDPGCreate messages).
+
+  auto m = op->get_req<MOSDPGCreate>();
+  ceph_assert(m->get_type() == MSG_OSD_PG_CREATE);
 
   dout(10) << "handle_pg_create " << *m << dendl;
 
@@ -8101,21 +9143,17 @@ void OSD::handle_pg_create(OpRequestRef op)
 
   op->mark_started();
 
+  const auto osdmap = get_osdmap();
   map<pg_t,utime_t>::const_iterator ci = m->ctimes.begin();
   for (map<pg_t,pg_create_t>::const_iterator p = m->mkpg.begin();
        p != m->mkpg.end();
        ++p, ++ci) {
-    assert(ci != m->ctimes.end() && ci->first == p->first);
+    ceph_assert(ci != m->ctimes.end() && ci->first == p->first);
     epoch_t created = p->second.created;
     if (p->second.split_bits) // Skip split pgs
       continue;
     pg_t on = p->first;
 
-    if (on.preferred() >= 0) {
-      dout(20) << "ignoring localized pg " << on << dendl;
-      continue;
-    }
-
     if (!osdmap->have_pg_pool(on.pool())) {
       dout(20) << "ignoring pg on deleted pool " << on << dendl;
       continue;
@@ -8123,12 +9161,16 @@ void OSD::handle_pg_create(OpRequestRef op)
 
     dout(20) << "mkpg " << on << " e" << created << "@" << ci->second << dendl;
 
+    spg_t pgid;
+    bool mapped = osdmap->get_primary_shard(on, &pgid);
+    ceph_assert(mapped);
+
     // is it still ours?
     vector<int> up, acting;
     int up_primary = -1;
     int acting_primary = -1;
     osdmap->pg_to_up_acting_osds(on, &up, &up_primary, &acting, &acting_primary);
-    int role = osdmap->calc_pg_role(whoami, acting, acting.size());
+    int role = osdmap->calc_pg_role(pg_shard_t(whoami, pgid.shard), acting);
 
     if (acting_primary != whoami) {
       dout(10) << "mkpg " << on << "  not acting_primary (" << acting_primary
@@ -8136,697 +9178,363 @@ void OSD::handle_pg_create(OpRequestRef op)
       continue;
     }
 
-    spg_t pgid;
-    bool mapped = osdmap->get_primary_shard(on, &pgid);
-    assert(mapped);
 
-    PastIntervals pi(
-      osdmap->get_pools().at(pgid.pool()).ec_pool(),
-      *osdmap);
+    PastIntervals pi;
     pg_history_t history;
     build_initial_pg_history(pgid, created, ci->second, &history, &pi);
 
-    // The mon won't resend unless the primary changed, so
-    // we ignore same_interval_since.  We'll pass this history
-    // to handle_pg_peering_evt with the current epoch as the
-    // event -- the project_pg_history check in
-    // handle_pg_peering_evt will be a noop.
+    // The mon won't resend unless the primary changed, so we ignore
+    // same_interval_since.  We'll pass this history with the current
+    // epoch as the event.
     if (history.same_primary_since > m->epoch) {
       dout(10) << __func__ << ": got obsolete pg create on pgid "
-              << pgid << " from epoch " << m->epoch
-              << ", primary changed in " << history.same_primary_since
-              << dendl;
-      continue;
-    }
-
-    if (handle_pg_peering_evt(
-          pgid,
-          history,
-          pi,
-          osdmap->get_epoch(),
-          PG::CephPeeringEvtRef(
-           new PG::CephPeeringEvt(
-             osdmap->get_epoch(),
-             osdmap->get_epoch(),
-             PG::NullEvt()))
-          ) == -EEXIST) {
-      service.send_pg_created(pgid.pgid);
-    }
-  }
-  last_pg_create_epoch = m->epoch;
-
-  maybe_update_heartbeat_peers();
-}
-
-
-// ----------------------------------------
-// peering and recovery
-
-PG::RecoveryCtx OSD::create_context()
-{
-  ObjectStore::Transaction *t = new ObjectStore::Transaction;
-  C_Contexts *on_applied = new C_Contexts(cct);
-  C_Contexts *on_safe = new C_Contexts(cct);
-  map<int, map<spg_t,pg_query_t> > *query_map =
-    new map<int, map<spg_t, pg_query_t> >;
-  map<int,vector<pair<pg_notify_t, PastIntervals> > > *notify_list =
-    new map<int, vector<pair<pg_notify_t, PastIntervals> > >;
-  map<int,vector<pair<pg_notify_t, PastIntervals> > > *info_map =
-    new map<int,vector<pair<pg_notify_t, PastIntervals> > >;
-  PG::RecoveryCtx rctx(query_map, info_map, notify_list,
-                      on_applied, on_safe, t);
-  return rctx;
-}
-
-struct C_OpenPGs : public Context {
-  set<PGRef> pgs;
-  ObjectStore *store;
-  OSD *osd;
-  C_OpenPGs(set<PGRef>& p, ObjectStore *s, OSD* o) : store(s), osd(o) {
-    pgs.swap(p);
-  }
-  void finish(int r) override {
-    RWLock::RLocker l(osd->pg_map_lock);
-    for (auto p : pgs) {
-      if (osd->pg_map.count(p->info.pgid)) {
-        p->ch = store->open_collection(p->coll);
-        assert(p->ch);
-      }
-    }
-  }
-};
-
-void OSD::dispatch_context_transaction(PG::RecoveryCtx &ctx, PG *pg,
-                                       ThreadPool::TPHandle *handle)
-{
-  if (!ctx.transaction->empty()) {
-    if (!ctx.created_pgs.empty()) {
-      ctx.on_applied->add(new C_OpenPGs(ctx.created_pgs, store, this));
-    }
-    int tr = store->queue_transaction(
-      pg->osr.get(),
-      std::move(*ctx.transaction), ctx.on_applied, ctx.on_safe, NULL,
-      TrackedOpRef(), handle);
-    delete (ctx.transaction);
-    assert(tr == 0);
-    ctx.transaction = new ObjectStore::Transaction;
-    ctx.on_applied = new C_Contexts(cct);
-    ctx.on_safe = new C_Contexts(cct);
-  }
-}
-
-void OSD::dispatch_context(PG::RecoveryCtx &ctx, PG *pg, OSDMapRef curmap,
-                           ThreadPool::TPHandle *handle)
-{
-  if (service.get_osdmap()->is_up(whoami) &&
-      is_active()) {
-    do_notifies(*ctx.notify_list, curmap);
-    do_queries(*ctx.query_map, curmap);
-    do_infos(*ctx.info_map, curmap);
-  }
-  delete ctx.notify_list;
-  delete ctx.query_map;
-  delete ctx.info_map;
-  if ((ctx.on_applied->empty() &&
-       ctx.on_safe->empty() &&
-       ctx.transaction->empty() &&
-       ctx.created_pgs.empty()) || !pg) {
-    delete ctx.transaction;
-    delete ctx.on_applied;
-    delete ctx.on_safe;
-    assert(ctx.created_pgs.empty());
-  } else {
-    if (!ctx.created_pgs.empty()) {
-      ctx.on_applied->add(new C_OpenPGs(ctx.created_pgs, store, this));
-    }
-    int tr = store->queue_transaction(
-      pg->osr.get(),
-      std::move(*ctx.transaction), ctx.on_applied, ctx.on_safe, NULL, TrackedOpRef(),
-      handle);
-    delete (ctx.transaction);
-    assert(tr == 0);
-  }
-}
-
-/** do_notifies
- * Send an MOSDPGNotify to a primary, with a list of PGs that I have
- * content for, and they are primary for.
- */
-
-void OSD::do_notifies(
-  map<int,vector<pair<pg_notify_t,PastIntervals> > >& notify_list,
-  OSDMapRef curmap)
-{
-  for (map<int,
-          vector<pair<pg_notify_t,PastIntervals> > >::iterator it =
-        notify_list.begin();
-       it != notify_list.end();
-       ++it) {
-    if (!curmap->is_up(it->first)) {
-      dout(20) << __func__ << " skipping down osd." << it->first << dendl;
-      continue;
-    }
-    ConnectionRef con = service.get_con_osd_cluster(
-      it->first, curmap->get_epoch());
-    if (!con) {
-      dout(20) << __func__ << " skipping osd." << it->first
-              << " (NULL con)" << dendl;
-      continue;
-    }
-    service.share_map_peer(it->first, con.get(), curmap);
-    dout(7) << __func__ << " osd " << it->first
-           << " on " << it->second.size() << " PGs" << dendl;
-    MOSDPGNotify *m = new MOSDPGNotify(curmap->get_epoch(),
-                                      it->second);
-    con->send_message(m);
-  }
-}
-
-
-/** do_queries
- * send out pending queries for info | summaries
- */
-void OSD::do_queries(map<int, map<spg_t,pg_query_t> >& query_map,
-                    OSDMapRef curmap)
-{
-  for (map<int, map<spg_t,pg_query_t> >::iterator pit = query_map.begin();
-       pit != query_map.end();
-       ++pit) {
-    if (!curmap->is_up(pit->first)) {
-      dout(20) << __func__ << " skipping down osd." << pit->first << dendl;
-      continue;
-    }
-    int who = pit->first;
-    ConnectionRef con = service.get_con_osd_cluster(who, curmap->get_epoch());
-    if (!con) {
-      dout(20) << __func__ << " skipping osd." << who
-              << " (NULL con)" << dendl;
-      continue;
-    }
-    service.share_map_peer(who, con.get(), curmap);
-    dout(7) << __func__ << " querying osd." << who
-           << " on " << pit->second.size() << " PGs" << dendl;
-    MOSDPGQuery *m = new MOSDPGQuery(curmap->get_epoch(), pit->second);
-    con->send_message(m);
-  }
-}
-
-
-void OSD::do_infos(map<int,
-                      vector<pair<pg_notify_t, PastIntervals> > >& info_map,
-                  OSDMapRef curmap)
-{
-  for (map<int,
-          vector<pair<pg_notify_t, PastIntervals> > >::iterator p =
-        info_map.begin();
-       p != info_map.end();
-       ++p) {
-    if (!curmap->is_up(p->first)) {
-      dout(20) << __func__ << " skipping down osd." << p->first << dendl;
-      continue;
-    }
-    for (vector<pair<pg_notify_t,PastIntervals> >::iterator i = p->second.begin();
-        i != p->second.end();
-        ++i) {
-      dout(20) << __func__ << " sending info " << i->first.info
-              << " to shard " << p->first << dendl;
-    }
-    ConnectionRef con = service.get_con_osd_cluster(
-      p->first, curmap->get_epoch());
-    if (!con) {
-      dout(20) << __func__ << " skipping osd." << p->first
-              << " (NULL con)" << dendl;
-      continue;
-    }
-    service.share_map_peer(p->first, con.get(), curmap);
-    MOSDPGInfo *m = new MOSDPGInfo(curmap->get_epoch());
-    m->pg_list = p->second;
-    con->send_message(m);
-  }
-  info_map.clear();
-}
-
-
-/** PGNotify
- * from non-primary to primary
- * includes pg_info_t.
- * NOTE: called with opqueue active.
- */
-void OSD::handle_pg_notify(OpRequestRef op)
-{
-  const MOSDPGNotify *m = static_cast<const MOSDPGNotify*>(op->get_req());
-  assert(m->get_type() == MSG_OSD_PG_NOTIFY);
-
-  dout(7) << "handle_pg_notify from " << m->get_source() << dendl;
-  int from = m->get_source().num();
-
-  if (!require_osd_peer(op->get_req()))
-    return;
-
-  if (!require_same_or_newer_map(op, m->get_epoch(), false))
-    return;
-
-  op->mark_started();
-
-  for (auto it = m->get_pg_list().begin();
-       it != m->get_pg_list().end();
-       ++it) {
-    if (it->first.info.pgid.preferred() >= 0) {
-      dout(20) << "ignoring localized pg " << it->first.info.pgid << dendl;
+              << pgid << " from epoch " << m->epoch
+              << ", primary changed in " << history.same_primary_since
+              << dendl;
       continue;
     }
-
-    handle_pg_peering_evt(
-      spg_t(it->first.info.pgid.pgid, it->first.to),
-      it->first.info.history, it->second,
-      it->first.query_epoch,
-      PG::CephPeeringEvtRef(
-       new PG::CephPeeringEvt(
-         it->first.epoch_sent, it->first.query_epoch,
-         PG::MNotifyRec(pg_shard_t(from, it->first.from), it->first,
-          op->get_req()->get_connection()->get_features())))
-      );
+    enqueue_peering_evt(
+      pgid,
+      PGPeeringEventRef(
+       std::make_shared<PGPeeringEvent>(
+         osdmap->get_epoch(),
+         osdmap->get_epoch(),
+         NullEvt(),
+         true,
+         new PGCreateInfo(
+           pgid,
+           osdmap->get_epoch(),
+           history,
+           pi,
+           true)
+         )));
   }
-}
 
-void OSD::handle_pg_log(OpRequestRef op)
-{
-  MOSDPGLog *m = static_cast<MOSDPGLog*>(op->get_nonconst_req());
-  assert(m->get_type() == MSG_OSD_PG_LOG);
-  dout(7) << "handle_pg_log " << *m << " from " << m->get_source() << dendl;
-
-  if (!require_osd_peer(op->get_req()))
-    return;
-
-  int from = m->get_source().num();
-  if (!require_same_or_newer_map(op, m->get_epoch(), false))
-    return;
-
-  if (m->info.pgid.preferred() >= 0) {
-    dout(10) << "ignoring localized pg " << m->info.pgid << dendl;
-    return;
+  {
+    std::lock_guard l(pending_creates_lock);
+    if (pending_creates_from_mon == 0) {
+      last_pg_create_epoch = m->epoch;
+    }
   }
 
-  op->mark_started();
-  handle_pg_peering_evt(
-    spg_t(m->info.pgid.pgid, m->to),
-    m->info.history, m->past_intervals, m->get_epoch(),
-    PG::CephPeeringEvtRef(
-      new PG::CephPeeringEvt(
-       m->get_epoch(), m->get_query_epoch(),
-       PG::MLogRec(pg_shard_t(from, m->from), m)))
-    );
+  maybe_update_heartbeat_peers();
 }
 
-void OSD::handle_pg_info(OpRequestRef op)
-{
-  const MOSDPGInfo *m = static_cast<const MOSDPGInfo *>(op->get_req());
-  assert(m->get_type() == MSG_OSD_PG_INFO);
-  dout(7) << "handle_pg_info " << *m << " from " << m->get_source() << dendl;
-
-  if (!require_osd_peer(op->get_req()))
-    return;
 
-  int from = m->get_source().num();
-  if (!require_same_or_newer_map(op, m->get_epoch(), false))
-    return;
+// ----------------------------------------
+// peering and recovery
 
-  op->mark_started();
+PeeringCtx OSD::create_context()
+{
+  return PeeringCtx(get_osdmap()->require_osd_release);
+}
 
-  for (auto p = m->pg_list.begin();
-       p != m->pg_list.end();
-       ++p) {
-    if (p->first.info.pgid.preferred() >= 0) {
-      dout(10) << "ignoring localized pg " << p->first.info.pgid << dendl;
-      continue;
+void OSD::dispatch_context(PeeringCtx &ctx, PG *pg, OSDMapRef curmap,
+                           ThreadPool::TPHandle *handle)
+{
+  if (!service.get_osdmap()->is_up(whoami)) {
+    dout(20) << __func__ << " not up in osdmap" << dendl;
+  } else if (!is_active()) {
+    dout(20) << __func__ << " not active" << dendl;
+  } else {
+    for (auto& [osd, ls] : ctx.message_map) {
+      if (!curmap->is_up(osd)) {
+       dout(20) << __func__ << " skipping down osd." << osd << dendl;
+       continue;
+      }
+      ConnectionRef con = service.get_con_osd_cluster(
+       osd, curmap->get_epoch());
+      if (!con) {
+       dout(20) << __func__ << " skipping osd." << osd << " (NULL con)"
+                << dendl;
+       continue;
+      }
+      service.maybe_share_map(con.get(), curmap);
+      for (auto m : ls) {
+       con->send_message2(m);
+      }
+      ls.clear();
     }
-
-    handle_pg_peering_evt(
-      spg_t(p->first.info.pgid.pgid, p->first.to),
-      p->first.info.history, p->second, p->first.epoch_sent,
-      PG::CephPeeringEvtRef(
-       new PG::CephPeeringEvt(
-         p->first.epoch_sent, p->first.query_epoch,
-         PG::MInfoRec(
-           pg_shard_t(
-             from, p->first.from), p->first.info, p->first.epoch_sent)))
-      );
+  }
+  if ((!ctx.transaction.empty() || ctx.transaction.has_contexts()) && pg) {
+    int tr = store->queue_transaction(
+      pg->ch,
+      std::move(ctx.transaction), TrackedOpRef(),
+      handle);
+    ceph_assert(tr == 0);
   }
 }
 
-void OSD::handle_pg_trim(OpRequestRef op)
+void OSD::handle_fast_pg_create(MOSDPGCreate2 *m)
 {
-  const MOSDPGTrim *m = static_cast<const MOSDPGTrim*>(op->get_req());
-  assert(m->get_type() == MSG_OSD_PG_TRIM);
-
-  dout(7) << "handle_pg_trim " << *m << " from " << m->get_source() << dendl;
-
-  if (!require_osd_peer(op->get_req()))
-    return;
-
-  int from = m->get_source().num();
-  if (!require_same_or_newer_map(op, m->epoch, false))
-    return;
-
-  if (m->pgid.preferred() >= 0) {
-    dout(10) << "ignoring localized pg " << m->pgid << dendl;
+  dout(7) << __func__ << " " << *m << " from " << m->get_source() << dendl;
+  if (!require_mon_peer(m)) {
+    m->put();
     return;
   }
-
-  op->mark_started();
-
-  PG *pg = _lookup_lock_pg(m->pgid);
-  if(!pg) {
-    dout(10) << " don't have pg " << m->pgid << dendl;
-    return;
+  for (auto& p : m->pgs) {
+    spg_t pgid = p.first;
+    epoch_t created = p.second.first;
+    utime_t created_stamp = p.second.second;
+    auto q = m->pg_extra.find(pgid);
+    if (q == m->pg_extra.end()) {
+      dout(20) << __func__ << " " << pgid << " e" << created
+              << "@" << created_stamp
+              << " (no history or past_intervals)" << dendl;
+      // pre-octopus ... no pg history.  this can be removed in Q release.
+      enqueue_peering_evt(
+       pgid,
+       PGPeeringEventRef(
+         std::make_shared<PGPeeringEvent>(
+           m->epoch,
+           m->epoch,
+           NullEvt(),
+           true,
+           new PGCreateInfo(
+             pgid,
+             created,
+             pg_history_t(created, created_stamp),
+             PastIntervals(),
+             true)
+           )));
+    } else {
+      dout(20) << __func__ << " " << pgid << " e" << created
+              << "@" << created_stamp
+              << " history " << q->second.first
+              << " pi " << q->second.second << dendl;
+      if (!q->second.second.empty() &&
+         m->epoch < q->second.second.get_bounds().second) {
+       clog->error() << "got pg_create on " << pgid << " epoch " << m->epoch
+                     << " and unmatched past_intervals " << q->second.second
+                     << " (history " << q->second.first << ")";
+      } else {
+       enqueue_peering_evt(
+         pgid,
+         PGPeeringEventRef(
+           std::make_shared<PGPeeringEvent>(
+             m->epoch,
+             m->epoch,
+             NullEvt(),
+             true,
+             new PGCreateInfo(
+               pgid,
+               m->epoch,
+               q->second.first,
+               q->second.second,
+               true)
+             )));
+      }
+    }
   }
 
-  if (m->epoch < pg->info.history.same_interval_since) {
-    dout(10) << *pg << " got old trim to " << m->trim_to << ", ignoring" << dendl;
-    pg->unlock();
-    return;
+  {
+    std::lock_guard l(pending_creates_lock);
+    if (pending_creates_from_mon == 0) {
+      last_pg_create_epoch = m->epoch;
+    }
   }
 
-  if (pg->is_primary()) {
-    // peer is informing us of their last_complete_ondisk
-    dout(10) << *pg << " replica osd." << from << " lcod " << m->trim_to << dendl;
-    pg->peer_last_complete_ondisk[pg_shard_t(from, m->pgid.shard)] =
-      m->trim_to;
-    // trim log when the pg is recovered
-    pg->calc_min_last_complete_ondisk();
-  } else {
-    // primary is instructing us to trim
-    ObjectStore::Transaction t;
-    pg->pg_log.trim(m->trim_to, pg->info);
-    pg->dirty_info = true;
-    pg->write_if_dirty(t);
-    int tr = store->queue_transaction(pg->osr.get(), std::move(t), NULL);
-    assert(tr == 0);
-  }
-  pg->unlock();
+  m->put();
 }
 
-void OSD::handle_pg_backfill_reserve(OpRequestRef op)
+void OSD::handle_fast_pg_query(MOSDPGQuery *m)
 {
-  const MBackfillReserve *m = static_cast<const MBackfillReserve*>(op->get_req());
-  assert(m->get_type() == MSG_OSD_BACKFILL_RESERVE);
-
-  if (!require_osd_peer(op->get_req()))
-    return;
-  if (!require_same_or_newer_map(op, m->query_epoch, false))
+  dout(7) << __func__ << " " << *m << " from " << m->get_source() << dendl;
+  if (!require_osd_peer(m)) {
+    m->put();
     return;
-
-  PG::CephPeeringEvtRef evt;
-  if (m->type == MBackfillReserve::REQUEST) {
-    evt = PG::CephPeeringEvtRef(
-      new PG::CephPeeringEvt(
-       m->query_epoch,
-       m->query_epoch,
-       PG::RequestBackfillPrio(m->priority)));
-  } else if (m->type == MBackfillReserve::GRANT) {
-    evt = PG::CephPeeringEvtRef(
-      new PG::CephPeeringEvt(
-       m->query_epoch,
-       m->query_epoch,
-       PG::RemoteBackfillReserved()));
-  } else if (m->type == MBackfillReserve::REJECT) {
-    evt = PG::CephPeeringEvtRef(
-      new PG::CephPeeringEvt(
-       m->query_epoch,
-       m->query_epoch,
-       PG::RemoteReservationRejected()));
-  } else {
-    ceph_abort();
   }
-
-  if (service.splitting(m->pgid)) {
-    peering_wait_for_split[m->pgid].push_back(evt);
-    return;
+  int from = m->get_source().num();
+  for (auto& p : m->pg_list) {
+    enqueue_peering_evt(
+      p.first,
+      PGPeeringEventRef(
+       std::make_shared<PGPeeringEvent>(
+         p.second.epoch_sent, p.second.epoch_sent,
+         MQuery(
+           p.first,
+           pg_shard_t(from, p.second.from),
+           p.second,
+           p.second.epoch_sent),
+         false))
+      );
   }
+  m->put();
+}
 
-  PG *pg = _lookup_lock_pg(m->pgid);
-  if (!pg) {
-    dout(10) << " don't have pg " << m->pgid << dendl;
+void OSD::handle_fast_pg_notify(MOSDPGNotify* m)
+{
+  dout(7) << __func__ << " " << *m << " from " << m->get_source() << dendl;
+  if (!require_osd_peer(m)) {
+    m->put();
     return;
   }
-
-  pg->queue_peering_event(evt);
-  pg->unlock();
+  int from = m->get_source().num();
+  for (auto& p : m->get_pg_list()) {
+    spg_t pgid(p.info.pgid.pgid, p.to);
+    enqueue_peering_evt(
+      pgid,
+      PGPeeringEventRef(
+       std::make_shared<PGPeeringEvent>(
+         p.epoch_sent,
+         p.query_epoch,
+         MNotifyRec(
+           pgid, pg_shard_t(from, p.from),
+           p,
+           m->get_connection()->get_features()),
+         true,
+         new PGCreateInfo(
+           pgid,
+           p.query_epoch,
+           p.info.history,
+           p.past_intervals,
+           false)
+         )));
+  }
+  m->put();
 }
 
-void OSD::handle_pg_recovery_reserve(OpRequestRef op)
+void OSD::handle_fast_pg_info(MOSDPGInfo* m)
 {
-  const MRecoveryReserve *m = static_cast<const MRecoveryReserve*>(op->get_req());
-  assert(m->get_type() == MSG_OSD_RECOVERY_RESERVE);
-
-  if (!require_osd_peer(op->get_req()))
-    return;
-  if (!require_same_or_newer_map(op, m->query_epoch, false))
+  dout(7) << __func__ << " " << *m << " from " << m->get_source() << dendl;
+  if (!require_osd_peer(m)) {
+    m->put();
     return;
-
-  PG::CephPeeringEvtRef evt;
-  if (m->type == MRecoveryReserve::REQUEST) {
-    evt = PG::CephPeeringEvtRef(
-      new PG::CephPeeringEvt(
-       m->query_epoch,
-       m->query_epoch,
-       PG::RequestRecovery()));
-  } else if (m->type == MRecoveryReserve::GRANT) {
-    evt = PG::CephPeeringEvtRef(
-      new PG::CephPeeringEvt(
-       m->query_epoch,
-       m->query_epoch,
-       PG::RemoteRecoveryReserved()));
-  } else if (m->type == MRecoveryReserve::RELEASE) {
-    evt = PG::CephPeeringEvtRef(
-      new PG::CephPeeringEvt(
-       m->query_epoch,
-       m->query_epoch,
-       PG::RecoveryDone()));
-  } else {
-    ceph_abort();
   }
-
-  if (service.splitting(m->pgid)) {
-    peering_wait_for_split[m->pgid].push_back(evt);
-    return;
+  int from = m->get_source().num();
+  for (auto& p : m->pg_list) {
+    enqueue_peering_evt(
+      spg_t(p.info.pgid.pgid, p.to),
+      PGPeeringEventRef(
+       std::make_shared<PGPeeringEvent>(
+         p.epoch_sent, p.query_epoch,
+         MInfoRec(
+           pg_shard_t(from, p.from),
+           p.info,
+           p.epoch_sent)))
+      );
   }
+  m->put();
+}
 
-  PG *pg = _lookup_lock_pg(m->pgid);
-  if (!pg) {
-    dout(10) << " don't have pg " << m->pgid << dendl;
+void OSD::handle_fast_pg_remove(MOSDPGRemove *m)
+{
+  dout(7) << __func__ << " " << *m << " from " << m->get_source() << dendl;
+  if (!require_osd_peer(m)) {
+    m->put();
     return;
   }
-
-  pg->queue_peering_event(evt);
-  pg->unlock();
+  for (auto& pgid : m->pg_list) {
+    enqueue_peering_evt(
+      pgid,
+      PGPeeringEventRef(
+       std::make_shared<PGPeeringEvent>(
+         m->get_epoch(), m->get_epoch(),
+         PeeringState::DeleteStart())));
+  }
+  m->put();
 }
 
-
-/** PGQuery
- * from primary to replica | stray
- * NOTE: called with opqueue active.
- */
-void OSD::handle_pg_query(OpRequestRef op)
+void OSD::handle_fast_force_recovery(MOSDForceRecovery *m)
 {
-  assert(osd_lock.is_locked());
-
-  const MOSDPGQuery *m = static_cast<const MOSDPGQuery*>(op->get_req());
-  assert(m->get_type() == MSG_OSD_PG_QUERY);
-
-  if (!require_osd_peer(op->get_req()))
-    return;
-
-  dout(7) << "handle_pg_query from " << m->get_source() << " epoch " << m->get_epoch() << dendl;
-  int from = m->get_source().num();
-
-  if (!require_same_or_newer_map(op, m->get_epoch(), false))
+  dout(10) << __func__ << " " << *m << dendl;
+  if (!require_mon_or_mgr_peer(m)) {
+    m->put();
     return;
-
-  op->mark_started();
-
-  map< int, vector<pair<pg_notify_t, PastIntervals> > > notify_list;
-
-  for (auto it = m->pg_list.begin();
-       it != m->pg_list.end();
-       ++it) {
-    spg_t pgid = it->first;
-
-    if (pgid.preferred() >= 0) {
-      dout(10) << "ignoring localized pg " << pgid << dendl;
-      continue;
-    }
-
-    if (service.splitting(pgid)) {
-      peering_wait_for_split[pgid].push_back(
-       PG::CephPeeringEvtRef(
-         new PG::CephPeeringEvt(
-           it->second.epoch_sent, it->second.epoch_sent,
-           PG::MQuery(pg_shard_t(from, it->second.from),
-                      it->second, it->second.epoch_sent))));
-      continue;
-    }
-
-    {
-      RWLock::RLocker l(pg_map_lock);
-      if (pg_map.count(pgid)) {
-        PG *pg = 0;
-        pg = _lookup_lock_pg_with_map_lock_held(pgid);
-        pg->queue_query(
-            it->second.epoch_sent, it->second.epoch_sent,
-            pg_shard_t(from, it->second.from), it->second);
-        pg->unlock();
-        continue;
+  }
+  epoch_t epoch = get_osdmap_epoch();
+  for (auto pgid : m->forced_pgs) {
+    if (m->options & OFR_BACKFILL) {
+      if (m->options & OFR_CANCEL) {
+       enqueue_peering_evt(
+         pgid,
+         PGPeeringEventRef(
+           std::make_shared<PGPeeringEvent>(
+             epoch, epoch,
+             PeeringState::UnsetForceBackfill())));
+      } else {
+       enqueue_peering_evt(
+         pgid,
+         PGPeeringEventRef(
+           std::make_shared<PGPeeringEvent>(
+             epoch, epoch,
+             PeeringState::SetForceBackfill())));
       }
-    }
-
-    if (!osdmap->have_pg_pool(pgid.pool()))
-      continue;
-
-    // get active crush mapping
-    int up_primary, acting_primary;
-    vector<int> up, acting;
-    osdmap->pg_to_up_acting_osds(
-      pgid.pgid, &up, &up_primary, &acting, &acting_primary);
-
-    // same primary?
-    pg_history_t history = it->second.history;
-    bool valid_history = project_pg_history(
-      pgid, history, it->second.epoch_sent,
-      up, up_primary, acting, acting_primary);
-
-    if (!valid_history ||
-        it->second.epoch_sent < history.same_interval_since) {
-      dout(10) << " pg " << pgid << " dne, and pg has changed in "
-              << history.same_interval_since
-              << " (msg from " << it->second.epoch_sent << ")" << dendl;
-      continue;
-    }
-
-    dout(10) << " pg " << pgid << " dne" << dendl;
-    pg_info_t empty(spg_t(pgid.pgid, it->second.to));
-    /* This is racy, but that should be ok: if we complete the deletion
-     * before the pg is recreated, we'll just start it off backfilling
-     * instead of just empty */
-    if (service.deleting_pgs.lookup(pgid))
-      empty.set_last_backfill(hobject_t());
-    if (it->second.type == pg_query_t::LOG ||
-       it->second.type == pg_query_t::FULLLOG) {
-      ConnectionRef con = service.get_con_osd_cluster(from, osdmap->get_epoch());
-      if (con) {
-       MOSDPGLog *mlog = new MOSDPGLog(
-         it->second.from, it->second.to,
-         osdmap->get_epoch(), empty,
-         it->second.epoch_sent);
-       service.share_map_peer(from, con.get(), osdmap);
-       con->send_message(mlog);
+    } else if (m->options & OFR_RECOVERY) {
+      if (m->options & OFR_CANCEL) {
+       enqueue_peering_evt(
+         pgid,
+         PGPeeringEventRef(
+           std::make_shared<PGPeeringEvent>(
+             epoch, epoch,
+             PeeringState::UnsetForceRecovery())));
+      } else {
+       enqueue_peering_evt(
+         pgid,
+         PGPeeringEventRef(
+           std::make_shared<PGPeeringEvent>(
+             epoch, epoch,
+             PeeringState::SetForceRecovery())));
       }
-    } else {
-      notify_list[from].push_back(
-       make_pair(
-         pg_notify_t(
-           it->second.from, it->second.to,
-           it->second.epoch_sent,
-           osdmap->get_epoch(),
-           empty),
-         PastIntervals(
-           osdmap->get_pools().at(pgid.pool()).ec_pool(),
-           *osdmap)));
     }
   }
-  do_notifies(notify_list, osdmap);
+  m->put();
 }
 
-
-void OSD::handle_pg_remove(OpRequestRef op)
+void OSD::handle_pg_query_nopg(const MQuery& q)
 {
-  const MOSDPGRemove *m = static_cast<const MOSDPGRemove *>(op->get_req());
-  assert(m->get_type() == MSG_OSD_PG_REMOVE);
-  assert(osd_lock.is_locked());
-
-  if (!require_osd_peer(op->get_req()))
-    return;
+  spg_t pgid = q.pgid;
+  dout(10) << __func__ << " " << pgid << dendl;
 
-  dout(7) << "handle_pg_remove from " << m->get_source() << " on "
-         << m->pg_list.size() << " pgs" << dendl;
-
-  if (!require_same_or_newer_map(op, m->get_epoch(), false))
+  OSDMapRef osdmap = get_osdmap();
+  if (!osdmap->have_pg_pool(pgid.pool()))
     return;
 
-  op->mark_started();
-
-  for (auto it = m->pg_list.begin();
-       it != m->pg_list.end();
-       ++it) {
-    spg_t pgid = *it;
-    if (pgid.preferred() >= 0) {
-      dout(10) << "ignoring localized pg " << pgid << dendl;
-      continue;
-    }
-
-    RWLock::WLocker l(pg_map_lock);
-    if (pg_map.count(pgid) == 0) {
-      dout(10) << " don't have pg " << pgid << dendl;
-      continue;
-    }
-    dout(5) << "queue_pg_for_deletion: " << pgid << dendl;
-    PG *pg = _lookup_lock_pg_with_map_lock_held(pgid);
-    pg_history_t history = pg->info.history;
-    int up_primary, acting_primary;
-    vector<int> up, acting;
-    osdmap->pg_to_up_acting_osds(
-      pgid.pgid, &up, &up_primary, &acting, &acting_primary);
-    bool valid_history = project_pg_history(
-      pg->info.pgid, history, pg->get_osdmap()->get_epoch(),
-      up, up_primary, acting, acting_primary);
-    if (valid_history &&
-        history.same_interval_since <= m->get_epoch()) {
-      assert(pg->get_primary().osd == m->get_source().num());
-      PGRef _pg(pg);
-      _remove_pg(pg);
-      pg->unlock();
+  dout(10) << " pg " << pgid << " dne" << dendl;
+  pg_info_t empty(spg_t(pgid.pgid, q.query.to));
+  ConnectionRef con = service.get_con_osd_cluster(q.from.osd, osdmap->get_epoch());
+  if (con) {
+    Message *m;
+    if (q.query.type == pg_query_t::LOG ||
+       q.query.type == pg_query_t::FULLLOG) {
+      m = new MOSDPGLog(
+       q.query.from, q.query.to,
+       osdmap->get_epoch(), empty,
+       q.query.epoch_sent);
     } else {
-      dout(10) << *pg << " ignoring remove request, pg changed in epoch "
-              << history.same_interval_since
-              << " > " << m->get_epoch() << dendl;
-      pg->unlock();
-    }
+      vector<pg_notify_t> ls;
+      ls.push_back(
+       pg_notify_t(
+         q.query.from, q.query.to,
+         q.query.epoch_sent,
+         osdmap->get_epoch(),
+         empty,
+         PastIntervals()));
+      m = new MOSDPGNotify(osdmap->get_epoch(), std::move(ls));
+    }
+    service.maybe_share_map(con.get(), osdmap);
+    con->send_message(m);
   }
 }
 
-void OSD::_remove_pg(PG *pg)
+void OSDService::queue_check_readable(spg_t spgid,
+                                     epoch_t lpr,
+                                     ceph::signedspan delay)
 {
-  ObjectStore::Transaction rmt ;
-
-  // on_removal, which calls remove_watchers_and_notifies, and the erasure from
-  // the pg_map must be done together without unlocking the pg lock,
-  // to avoid racing with watcher cleanup in ms_handle_reset
-  // and handle_notify_timeout
-  pg->on_removal(&rmt);
-
-  service.cancel_pending_splits_for_parent(pg->info.pgid);
-  int tr = store->queue_transaction(
-    pg->osr.get(), std::move(rmt), NULL, 
-    new ContainerContext<
-      SequencerRef>(pg->osr));
-  assert(tr == 0);
-
-  DeletingStateRef deleting = service.deleting_pgs.lookup_or_create(
-    pg->info.pgid,
-    make_pair(
-      pg->info.pgid,
-      PGRef(pg))
-    );
-  remove_wq.queue(make_pair(PGRef(pg), deleting));
-
-  service.pg_remove_epoch(pg->info.pgid);
-
-  // dereference from op_wq
-  op_shardedwq.clear_pg_pointer(pg->info.pgid);
-
-  // remove from map
-  pg_map.erase(pg->info.pgid);
-  pg->put("PGMap"); // since we've taken it out of map
+  if (delay == ceph::signedspan::zero()) {
+    osd->enqueue_peering_evt(
+      spgid,
+      PGPeeringEventRef(
+       std::make_shared<PGPeeringEvent>(
+         lpr, lpr,
+         PeeringState::CheckReadable())));
+  } else {
+    mono_timer.add_event(
+      delay,
+      [this, spgid, lpr]() {
+       queue_check_readable(spgid, lpr);
+      });
+  }
 }
 
 
@@ -8834,15 +9542,18 @@ void OSD::_remove_pg(PG *pg)
 // RECOVERY
 
 void OSDService::_maybe_queue_recovery() {
-  assert(recovery_lock.is_locked_by_me());
+  ceph_assert(ceph_mutex_is_locked_by_me(recovery_lock));
   uint64_t available_pushes;
   while (!awaiting_throttle.empty() &&
         _recover_now(&available_pushes)) {
-    uint64_t to_start = MIN(
+    uint64_t to_start = std::min(
       available_pushes,
       cct->_conf->osd_recovery_max_single_start);
     _queue_for_recovery(awaiting_throttle.front(), to_start);
     awaiting_throttle.pop_front();
+    dout(10) << __func__ << " starting " << to_start
+            << ", recovery_ops_reserved " << recovery_ops_reserved
+            << " -> " << (recovery_ops_reserved + to_start) << dendl;
     recovery_ops_reserved += to_start;
   }
 }
@@ -8862,7 +9573,7 @@ bool OSDService::_recover_now(uint64_t *available_pushes)
     return false;
   }
 
-  uint64_t max = cct->_conf->osd_recovery_max_active;
+  uint64_t max = osd->get_recovery_max_active();
   if (max <= recovery_ops_active + recovery_ops_reserved) {
     dout(15) << __func__ << " active " << recovery_ops_active
             << " + reserved " << recovery_ops_reserved
@@ -8876,108 +9587,135 @@ bool OSDService::_recover_now(uint64_t *available_pushes)
   return true;
 }
 
+unsigned OSDService::get_target_pg_log_entries() const
+{
+  auto num_pgs = osd->get_num_pgs();
+  auto target = cct->_conf->osd_target_pg_log_entries_per_osd;
+  if (num_pgs > 0 && target > 0) {
+    // target an even spread of our budgeted log entries across all
+    // PGs.  note that while we only get to control the entry count
+    // for primary PGs, we'll normally be responsible for a mix of
+    // primary and replica PGs (for the same pool(s) even), so this
+    // will work out.
+    return std::max<unsigned>(
+      std::min<unsigned>(target / num_pgs,
+                        cct->_conf->osd_max_pg_log_entries),
+      cct->_conf->osd_min_pg_log_entries);
+  } else {
+    // fall back to a per-pg value.
+    return cct->_conf->osd_min_pg_log_entries;
+  }
+}
+
 void OSD::do_recovery(
   PG *pg, epoch_t queued, uint64_t reserved_pushes,
   ThreadPool::TPHandle &handle)
 {
   uint64_t started = 0;
-  if (cct->_conf->osd_recovery_sleep > 0) {
-    handle.suspend_tp_timeout();
-    pg->unlock();
-    utime_t t;
-    t.set_from_double(cct->_conf->osd_recovery_sleep);
-    t.sleep();
-    dout(20) << __func__ << " slept for " << t << dendl;
-    pg->lock();
-    handle.reset_tp_timeout();
+
+  /*
+   * When the value of osd_recovery_sleep is set greater than zero, recovery
+   * ops are scheduled after osd_recovery_sleep amount of time from the previous
+   * recovery event's schedule time. This is done by adding a
+   * recovery_requeue_callback event, which re-queues the recovery op using
+   * queue_recovery_after_sleep.
+   */
+  float recovery_sleep = get_osd_recovery_sleep();
+  {
+    std::lock_guard l(service.sleep_lock);
+    if (recovery_sleep > 0 && service.recovery_needs_sleep) {
+      PGRef pgref(pg);
+      auto recovery_requeue_callback = new LambdaContext([this, pgref, queued, reserved_pushes](int r) {
+        dout(20) << "do_recovery wake up at "
+                 << ceph_clock_now()
+                << ", re-queuing recovery" << dendl;
+       std::lock_guard l(service.sleep_lock);
+        service.recovery_needs_sleep = false;
+        service.queue_recovery_after_sleep(pgref.get(), queued, reserved_pushes);
+      });
+
+      // This is true for the first recovery op and when the previous recovery op
+      // has been scheduled in the past. The next recovery op is scheduled after
+      // completing the sleep from now.
+
+      if (auto now = ceph::real_clock::now();
+         service.recovery_schedule_time < now) {
+        service.recovery_schedule_time = now;
+      }
+      service.recovery_schedule_time += ceph::make_timespan(recovery_sleep);
+      service.sleep_timer.add_event_at(service.recovery_schedule_time,
+                                      recovery_requeue_callback);
+      dout(20) << "Recovery event scheduled at "
+               << service.recovery_schedule_time << dendl;
+      return;
+    }
   }
 
   {
+    {
+      std::lock_guard l(service.sleep_lock);
+      service.recovery_needs_sleep = true;
+    }
+
     if (pg->pg_has_reset_since(queued)) {
       goto out;
     }
 
-    assert(!pg->deleting);
-    assert(pg->is_peered() && pg->is_primary());
-
-    assert(pg->recovery_queued);
-    pg->recovery_queued = false;
-
     dout(10) << "do_recovery starting " << reserved_pushes << " " << *pg << dendl;
 #ifdef DEBUG_RECOVERY_OIDS
-    dout(20) << "  active was " << service.recovery_oids[pg->info.pgid] << dendl;
+    dout(20) << "  active was " << service.recovery_oids[pg->pg_id] << dendl;
 #endif
 
-    bool more = pg->start_recovery_ops(reserved_pushes, handle, &started);
-    dout(10) << "do_recovery started " << started << "/" << reserved_pushes 
+    bool do_unfound = pg->start_recovery_ops(reserved_pushes, handle, &started);
+    dout(10) << "do_recovery started " << started << "/" << reserved_pushes
             << " on " << *pg << dendl;
 
-    // If no recovery op is started, don't bother to manipulate the RecoveryCtx
-    if (!started && (more || !pg->have_unfound())) {
-      goto out;
-    }
-
-    PG::RecoveryCtx rctx = create_context();
-    rctx.handle = &handle;
-
-    /*
-     * if we couldn't start any recovery ops and things are still
-     * unfound, see if we can discover more missing object locations.
-     * It may be that our initial locations were bad and we errored
-     * out while trying to pull.
-     */
-    if (!more && pg->have_unfound()) {
-      pg->discover_all_missing(*rctx.query_map);
-      if (rctx.query_map->empty()) {
-       dout(10) << "do_recovery  no luck, giving up on this pg for now" << dendl;
-      } else {
-       dout(10) << "do_recovery  no luck, giving up on this pg for now" << dendl;
-       pg->queue_recovery();
-      }
+    if (do_unfound) {
+      PeeringCtx rctx = create_context();
+      rctx.handle = &handle;
+      pg->find_unfound(queued, rctx);
+      dispatch_context(rctx, pg, pg->get_osdmap());
     }
-
-    pg->write_if_dirty(*rctx.transaction);
-    OSDMapRef curmap = pg->get_osdmap();
-    dispatch_context(rctx, pg, curmap);
   }
 
  out:
-  assert(started <= reserved_pushes);
+  ceph_assert(started <= reserved_pushes);
   service.release_reserved_pushes(reserved_pushes);
 }
 
 void OSDService::start_recovery_op(PG *pg, const hobject_t& soid)
 {
-  Mutex::Locker l(recovery_lock);
+  std::lock_guard l(recovery_lock);
   dout(10) << "start_recovery_op " << *pg << " " << soid
           << " (" << recovery_ops_active << "/"
-          << cct->_conf->osd_recovery_max_active << " rops)"
+          << osd->get_recovery_max_active() << " rops)"
           << dendl;
   recovery_ops_active++;
 
 #ifdef DEBUG_RECOVERY_OIDS
-  dout(20) << "  active was " << recovery_oids[pg->info.pgid] << dendl;
-  assert(recovery_oids[pg->info.pgid].count(soid) == 0);
-  recovery_oids[pg->info.pgid].insert(soid);
+  dout(20) << "  active was " << recovery_oids[pg->pg_id] << dendl;
+  ceph_assert(recovery_oids[pg->pg_id].count(soid) == 0);
+  recovery_oids[pg->pg_id].insert(soid);
 #endif
 }
 
 void OSDService::finish_recovery_op(PG *pg, const hobject_t& soid, bool dequeue)
 {
-  Mutex::Locker l(recovery_lock);
+  std::lock_guard l(recovery_lock);
   dout(10) << "finish_recovery_op " << *pg << " " << soid
           << " dequeue=" << dequeue
-          << " (" << recovery_ops_active << "/" << cct->_conf->osd_recovery_max_active << " rops)"
+          << " (" << recovery_ops_active << "/"
+          << osd->get_recovery_max_active() << " rops)"
           << dendl;
 
   // adjust count
-  assert(recovery_ops_active > 0);
+  ceph_assert(recovery_ops_active > 0);
   recovery_ops_active--;
 
 #ifdef DEBUG_RECOVERY_OIDS
-  dout(20) << "  active oids was " << recovery_oids[pg->info.pgid] << dendl;
-  assert(recovery_oids[pg->info.pgid].count(soid));
-  recovery_oids[pg->info.pgid].erase(soid);
+  dout(20) << "  active oids was " << recovery_oids[pg->pg_id] << dendl;
+  ceph_assert(recovery_oids[pg->pg_id].count(soid));
+  recovery_oids[pg->pg_id].erase(soid);
 #endif
 
   _maybe_queue_recovery();
@@ -8985,10 +9723,21 @@ void OSDService::finish_recovery_op(PG *pg, const hobject_t& soid, bool dequeue)
 
 bool OSDService::is_recovery_active()
 {
-  if (recovery_ops_active > 0)
+  if (cct->_conf->osd_debug_pretend_recovery_active) {
     return true;
+  }
+  return local_reserver.has_reservation() || remote_reserver.has_reservation();
+}
 
-  return false;
+void OSDService::release_reserved_pushes(uint64_t pushes)
+{
+  std::lock_guard l(recovery_lock);
+  dout(10) << __func__ << "(" << pushes << "), recovery_ops_reserved "
+          << recovery_ops_reserved << " -> " << (recovery_ops_reserved-pushes)
+          << dendl;
+  ceph_assert(recovery_ops_reserved >= pushes);
+  recovery_ops_reserved -= pushes;
+  _maybe_queue_recovery();
 }
 
 // =========================================================
@@ -9004,22 +9753,64 @@ bool OSD::op_is_discardable(const MOSDOp *op)
   return false;
 }
 
-void OSD::enqueue_op(spg_t pg, OpRequestRef& op, epoch_t epoch)
+void OSD::enqueue_op(spg_t pg, OpRequestRef&& op, epoch_t epoch)
 {
-  utime_t latency = ceph_clock_now() - op->get_req()->get_recv_stamp();
-  dout(15) << "enqueue_op " << op << " prio " << op->get_req()->get_priority()
-          << " cost " << op->get_req()->get_cost()
+  const utime_t stamp = op->get_req()->get_recv_stamp();
+  const utime_t latency = ceph_clock_now() - stamp;
+  const unsigned priority = op->get_req()->get_priority();
+  const int cost = op->get_req()->get_cost();
+  const uint64_t owner = op->get_req()->get_source().num();
+  const int type = op->get_req()->get_type();
+
+  dout(15) << "enqueue_op " << op << " prio " << priority
+           << " type " << type
+          << " cost " << cost
           << " latency " << latency
           << " epoch " << epoch
           << " " << *(op->get_req()) << dendl;
   op->osd_trace.event("enqueue op");
-  op->osd_trace.keyval("priority", op->get_req()->get_priority());
-  op->osd_trace.keyval("cost", op->get_req()->get_cost());
+  op->osd_trace.keyval("priority", priority);
+  op->osd_trace.keyval("cost", cost);
+#ifdef HAVE_JAEGER
+  if (op->osd_parent_span) {
+    auto enqueue_span = jaeger_tracing::child_span(__func__, op->osd_parent_span);
+    enqueue_span->Log({
+       {"priority", priority},
+       {"cost", cost},
+       {"epoch", epoch},
+       {"owner", owner},
+       {"type", type}
+       });
+  }
+#endif
   op->mark_queued_for_pg();
-  op_shardedwq.queue(make_pair(pg, PGQueueable(op, epoch)));
+  logger->tinc(l_osd_op_before_queue_op_lat, latency);
+  if (type == MSG_OSD_PG_PUSH ||
+      type == MSG_OSD_PG_PUSH_REPLY) {
+    op_shardedwq.queue(
+      OpSchedulerItem(
+        unique_ptr<OpSchedulerItem::OpQueueable>(new PGRecoveryMsg(pg, std::move(op))),
+        cost, priority, stamp, owner, epoch));
+  } else {
+    op_shardedwq.queue(
+      OpSchedulerItem(
+        unique_ptr<OpSchedulerItem::OpQueueable>(new PGOpItem(pg, std::move(op))),
+        cost, priority, stamp, owner, epoch));
+  }
 }
 
-
+void OSD::enqueue_peering_evt(spg_t pgid, PGPeeringEventRef evt)
+{
+  dout(15) << __func__ << " " << pgid << " " << evt->get_desc() << dendl;
+  op_shardedwq.queue(
+    OpSchedulerItem(
+      unique_ptr<OpSchedulerItem::OpQueueable>(new PGPeeringItem(pgid, evt)),
+      10,
+      cct->_conf->osd_peering_op_priority,
+      utime_t(),
+      0,
+      evt->get_epoch_sent()));
+}
 
 /*
  * NOTE: dequeue called in worker thread, with pg lock
@@ -9028,26 +9819,28 @@ void OSD::dequeue_op(
   PGRef pg, OpRequestRef op,
   ThreadPool::TPHandle &handle)
 {
-  FUNCTRACE();
-  OID_EVENT_TRACE_WITH_MSG(op->get_req(), "DEQUEUE_OP_BEGIN", false);
+  const Message *m = op->get_req();
+
+  FUNCTRACE(cct);
+  OID_EVENT_TRACE_WITH_MSG(m, "DEQUEUE_OP_BEGIN", false);
 
   utime_t now = ceph_clock_now();
   op->set_dequeued_time(now);
-  utime_t latency = now - op->get_req()->get_recv_stamp();
-  dout(10) << "dequeue_op " << op << " prio " << op->get_req()->get_priority()
-          << " cost " << op->get_req()->get_cost()
+
+  utime_t latency = now - m->get_recv_stamp();
+  dout(10) << "dequeue_op " << op << " prio " << m->get_priority()
+          << " cost " << m->get_cost()
           << " latency " << latency
-          << " " << *(op->get_req())
+          << " " << *m
           << " pg " << *pg << dendl;
 
-  Session *session = static_cast<Session *>(
-    op->get_req()->get_connection()->get_priv());
-  if (session) {
-    maybe_share_map(session, op, pg->get_osdmap());
-    session->put();
-  }
+  logger->tinc(l_osd_op_before_dequeue_op_lat, latency);
+
+  service.maybe_share_map(m->get_connection().get(),
+                         pg->get_osdmap(),
+                         op->sent_epoch);
 
-  if (pg->deleting)
+  if (pg->is_deleting())
     return;
 
   op->mark_reached_pg();
@@ -9057,90 +9850,64 @@ void OSD::dequeue_op(
 
   // finish
   dout(10) << "dequeue_op " << op << " finish" << dendl;
-  OID_EVENT_TRACE_WITH_MSG(op->get_req(), "DEQUEUE_OP_END", false);
+  OID_EVENT_TRACE_WITH_MSG(m, "DEQUEUE_OP_END", false);
 }
 
 
-struct C_CompleteSplits : public Context {
-  OSD *osd;
-  set<boost::intrusive_ptr<PG> > pgs;
-  C_CompleteSplits(OSD *osd, const set<boost::intrusive_ptr<PG> > &in)
-    : osd(osd), pgs(in) {}
-  void finish(int r) override {
-    Mutex::Locker l(osd->osd_lock);
-    if (osd->is_stopping())
-      return;
-    PG::RecoveryCtx rctx = osd->create_context();
-    for (set<boost::intrusive_ptr<PG> >::iterator i = pgs.begin();
-        i != pgs.end();
-        ++i) {
-      osd->pg_map_lock.get_write();
-      (*i)->lock();
-      osd->add_newly_split_pg(&**i, &rctx);
-      if (!((*i)->deleting)) {
-        set<spg_t> to_complete;
-        to_complete.insert((*i)->info.pgid);
-        osd->service.complete_split(to_complete);
-      }
-      osd->pg_map_lock.put_write();
-      osd->dispatch_context_transaction(rctx, &**i);
-      osd->wake_pg_waiters(*i);
-      (*i)->unlock();
-    }
-
-    osd->dispatch_context(rctx, 0, osd->service.get_osdmap());
-  }
-};
-
-void OSD::process_peering_events(
-  const list<PG*> &pgs,
-  ThreadPool::TPHandle &handle
-  )
+void OSD::dequeue_peering_evt(
+  OSDShard *sdata,
+  PG *pg,
+  PGPeeringEventRef evt,
+  ThreadPool::TPHandle& handle)
 {
+  PeeringCtx rctx = create_context();
+  auto curmap = sdata->get_osdmap();
   bool need_up_thru = false;
   epoch_t same_interval_since = 0;
-  OSDMapRef curmap;
-  PG::RecoveryCtx rctx = create_context();
-  rctx.handle = &handle;
-  for (list<PG*>::const_iterator i = pgs.begin();
-       i != pgs.end();
-       ++i) {
-    set<boost::intrusive_ptr<PG> > split_pgs;
-    PG *pg = *i;
-    pg->lock_suspend_timeout(handle);
-    curmap = service.get_osdmap();
-    if (pg->deleting) {
+  if (!pg) {
+    if (const MQuery *q = dynamic_cast<const MQuery*>(evt->evt.get())) {
+      handle_pg_query_nopg(*q);
+    } else {
+      derr << __func__ << " unrecognized pg-less event " << evt->get_desc() << dendl;
+      ceph_abort();
+    }
+  } else if (advance_pg(curmap->get_epoch(), pg, handle, rctx)) {
+    pg->do_peering_event(evt, rctx);
+    if (pg->is_deleted()) {
       pg->unlock();
-      continue;
+      return;
     }
-    if (!advance_pg(curmap->get_epoch(), pg, handle, &rctx, &split_pgs)) {
-      // we need to requeue the PG explicitly since we didn't actually
-      // handle an event
-      peering_wq.queue(pg);
-    } else {
-      assert(!pg->peering_queue.empty());
-      PG::CephPeeringEvtRef evt = pg->peering_queue.front();
-      pg->peering_queue.pop_front();
-      pg->handle_peering_event(evt, &rctx);
-    }
-    need_up_thru = pg->need_up_thru || need_up_thru;
-    same_interval_since = MAX(pg->info.history.same_interval_since,
-                             same_interval_since);
-    pg->write_if_dirty(*rctx.transaction);
-    if (!split_pgs.empty()) {
-      rctx.on_applied->add(new C_CompleteSplits(this, split_pgs));
-      split_pgs.clear();
-    }
-    dispatch_context_transaction(rctx, pg, &handle);
+    dispatch_context(rctx, pg, curmap, &handle);
+    need_up_thru = pg->get_need_up_thru();
+    same_interval_since = pg->get_same_interval_since();
     pg->unlock();
   }
-  if (need_up_thru)
+
+  if (need_up_thru) {
     queue_want_up_thru(same_interval_since);
-  dispatch_context(rctx, 0, curmap, &handle);
+  }
+
+  service.send_pg_temp();
+}
 
-  service.send_pg_temp();
+void OSD::dequeue_delete(
+  OSDShard *sdata,
+  PG *pg,
+  epoch_t e,
+  ThreadPool::TPHandle& handle)
+{
+  dequeue_peering_evt(
+    sdata,
+    pg,
+    PGPeeringEventRef(
+      std::make_shared<PGPeeringEvent>(
+       e, e,
+       PeeringState::DeleteSome())),
+    handle);
 }
 
+
+
 // --------------------------------
 
 const char** OSD::get_tracked_conf_keys() const
@@ -9148,14 +9915,33 @@ const char** OSD::get_tracked_conf_keys() const
   static const char* KEYS[] = {
     "osd_max_backfills",
     "osd_min_recovery_priority",
-    "osd_op_complaint_time", "osd_op_log_threshold",
-    "osd_op_history_size", "osd_op_history_duration",
+    "osd_max_trimming_pgs",
+    "osd_op_complaint_time",
+    "osd_op_log_threshold",
+    "osd_op_history_size",
+    "osd_op_history_duration",
+    "osd_op_history_slow_op_size",
+    "osd_op_history_slow_op_threshold",
     "osd_enable_op_tracker",
     "osd_map_cache_size",
-    "osd_map_max_advance",
+    "osd_pg_epoch_max_lag_factor",
     "osd_pg_epoch_persisted_max_stale",
-    "osd_disk_thread_ioprio_class",
-    "osd_disk_thread_ioprio_priority",
+    "osd_recovery_sleep",
+    "osd_recovery_sleep_hdd",
+    "osd_recovery_sleep_ssd",
+    "osd_recovery_sleep_hybrid",
+    "osd_delete_sleep",
+    "osd_delete_sleep_hdd",
+    "osd_delete_sleep_ssd",
+    "osd_delete_sleep_hybrid",
+    "osd_snap_trim_sleep",
+    "osd_snap_trim_sleep_hdd",
+    "osd_snap_trim_sleep_ssd",
+    "osd_snap_trim_sleep_hybrid"
+    "osd_scrub_sleep",
+    "osd_recovery_max_active",
+    "osd_recovery_max_active_hdd",
+    "osd_recovery_max_active_ssd",
     // clog & admin clog
     "clog_to_monitors",
     "clog_to_syslog",
@@ -9170,17 +9956,44 @@ const char** OSD::get_tracked_conf_keys() const
     "osd_recovery_delay_start",
     "osd_client_message_size_cap",
     "osd_client_message_cap",
+    "osd_heartbeat_min_size",
+    "osd_heartbeat_interval",
+    "osd_object_clean_region_max_num_intervals",
+    "osd_scrub_min_interval",
+    "osd_scrub_max_interval",
     NULL
   };
   return KEYS;
 }
 
-void OSD::handle_conf_change(const struct md_config_t *conf,
+void OSD::handle_conf_change(const ConfigProxy& conf,
                             const std::set <std::string> &changed)
 {
-  if (changed.count("osd_max_backfills")) {
-    service.local_reserver.set_max(cct->_conf->osd_max_backfills);
-    service.remote_reserver.set_max(cct->_conf->osd_max_backfills);
+  std::lock_guard l{osd_lock};
+
+  if (changed.count("osd_max_backfills") ||
+      changed.count("osd_delete_sleep") ||
+      changed.count("osd_delete_sleep_hdd") ||
+      changed.count("osd_delete_sleep_ssd") ||
+      changed.count("osd_delete_sleep_hybrid") ||
+      changed.count("osd_snap_trim_sleep") ||
+      changed.count("osd_snap_trim_sleep_hdd") ||
+      changed.count("osd_snap_trim_sleep_ssd") ||
+      changed.count("osd_snap_trim_sleep_hybrid") ||
+      changed.count("osd_scrub_sleep") ||
+      changed.count("osd_recovery_sleep") ||
+      changed.count("osd_recovery_sleep_hdd") ||
+      changed.count("osd_recovery_sleep_ssd") ||
+      changed.count("osd_recovery_sleep_hybrid") ||
+      changed.count("osd_recovery_max_active") ||
+      changed.count("osd_recovery_max_active_hdd") ||
+      changed.count("osd_recovery_max_active_ssd")) {
+    if (!maybe_override_options_for_qos() &&
+        changed.count("osd_max_backfills")) {
+      // Scheduler is not "mclock". Fallback to earlier behavior
+      service.local_reserver.set_max(cct->_conf->osd_max_backfills);
+      service.remote_reserver.set_max(cct->_conf->osd_max_backfills);
+    }
   }
   if (changed.count("osd_min_recovery_priority")) {
     service.local_reserver.set_min_priority(cct->_conf->osd_min_recovery_priority);
@@ -9207,10 +10020,6 @@ void OSD::handle_conf_change(const struct md_config_t *conf,
   if (changed.count("osd_enable_op_tracker")) {
       op_tracker.set_tracking(cct->_conf->osd_enable_op_tracker);
   }
-  if (changed.count("osd_disk_thread_ioprio_class") ||
-      changed.count("osd_disk_thread_ioprio_priority")) {
-    set_disk_tp_priority();
-  }
   if (changed.count("osd_map_cache_size")) {
     service.map_cache.set_size(cct->_conf->osd_map_cache_size);
     service.map_bl_cache.set_size(cct->_conf->osd_map_cache_size);
@@ -9227,6 +10036,10 @@ void OSD::handle_conf_change(const struct md_config_t *conf,
       changed.count("fsid")) {
     update_log_config();
   }
+  if (changed.count("osd_pg_epoch_max_lag_factor")) {
+    m_osd_pg_epoch_max_lag_factor = conf.get_val<double>(
+      "osd_pg_epoch_max_lag_factor");
+  }
 
 #ifdef HAVE_LIBFUSE
   if (changed.count("osd_objectstore_fuse")) {
@@ -9255,8 +10068,68 @@ void OSD::handle_conf_change(const struct md_config_t *conf,
       pol.throttler_bytes->reset_max(newval);
     }
   }
+  if (changed.count("osd_object_clean_region_max_num_intervals")) {
+    ObjectCleanRegions::set_max_num_intervals(cct->_conf->osd_object_clean_region_max_num_intervals);
+  }
 
+  if (changed.count("osd_scrub_min_interval") ||
+      changed.count("osd_scrub_max_interval")) {
+    resched_all_scrubs();
+    dout(0) << __func__ << ": scrub interval change" << dendl;
+  }
   check_config();
+  if (changed.count("osd_asio_thread_count")) {
+    service.poolctx.stop();
+    service.poolctx.start(conf.get_val<std::uint64_t>("osd_asio_thread_count"));
+  }
+}
+
+bool OSD::maybe_override_options_for_qos()
+{
+  // If the scheduler enabled is mclock, override the recovery, backfill
+  // and sleep options so that mclock can meet the QoS goals.
+  if (cct->_conf.get_val<std::string>("osd_op_queue") == "mclock_scheduler") {
+    dout(1) << __func__
+            << ": Changing recovery/backfill/sleep settings for QoS" << dendl;
+
+    // Set high value for recovery max active
+    uint32_t rec_max_active = 1000;
+    cct->_conf.set_val(
+      "osd_recovery_max_active", std::to_string(rec_max_active));
+    cct->_conf.set_val(
+      "osd_recovery_max_active_hdd", std::to_string(rec_max_active));
+    cct->_conf.set_val(
+      "osd_recovery_max_active_ssd", std::to_string(rec_max_active));
+
+    // Set high value for osd_max_backfill
+    uint32_t max_backfills = 1000;
+    cct->_conf.set_val("osd_max_backfills", std::to_string(max_backfills));
+    service.local_reserver.set_max(max_backfills);
+    service.remote_reserver.set_max(max_backfills);
+
+    // Disable recovery sleep
+    cct->_conf.set_val("osd_recovery_sleep", std::to_string(0));
+    cct->_conf.set_val("osd_recovery_sleep_hdd", std::to_string(0));
+    cct->_conf.set_val("osd_recovery_sleep_ssd", std::to_string(0));
+    cct->_conf.set_val("osd_recovery_sleep_hybrid", std::to_string(0));
+
+    // Disable delete sleep
+    cct->_conf.set_val("osd_delete_sleep", std::to_string(0));
+    cct->_conf.set_val("osd_delete_sleep_hdd", std::to_string(0));
+    cct->_conf.set_val("osd_delete_sleep_ssd", std::to_string(0));
+    cct->_conf.set_val("osd_delete_sleep_hybrid", std::to_string(0));
+
+    // Disable snap trim sleep
+    cct->_conf.set_val("osd_snap_trim_sleep", std::to_string(0));
+    cct->_conf.set_val("osd_snap_trim_sleep_hdd", std::to_string(0));
+    cct->_conf.set_val("osd_snap_trim_sleep_ssd", std::to_string(0));
+    cct->_conf.set_val("osd_snap_trim_sleep_hybrid", std::to_string(0));
+
+    // Disable scrub sleep
+    cct->_conf.set_val("osd_scrub_sleep", std::to_string(0));
+    return true;
+  }
+  return false;
 }
 
 void OSD::update_log_config()
@@ -9285,35 +10158,16 @@ void OSD::update_log_config()
 void OSD::check_config()
 {
   // some sanity checks
-  if (cct->_conf->osd_map_cache_size <= cct->_conf->osd_map_max_advance + 2) {
-    clog->warn() << "osd_map_cache_size (" << cct->_conf->osd_map_cache_size << ")"
-               << " is not > osd_map_max_advance ("
-               << cct->_conf->osd_map_max_advance << ")";
-  }
   if (cct->_conf->osd_map_cache_size <= (int)cct->_conf->osd_pg_epoch_persisted_max_stale + 2) {
     clog->warn() << "osd_map_cache_size (" << cct->_conf->osd_map_cache_size << ")"
                 << " is not > osd_pg_epoch_persisted_max_stale ("
                 << cct->_conf->osd_pg_epoch_persisted_max_stale << ")";
   }
-}
-
-void OSD::set_disk_tp_priority()
-{
-  dout(10) << __func__
-          << " class " << cct->_conf->osd_disk_thread_ioprio_class
-          << " priority " << cct->_conf->osd_disk_thread_ioprio_priority
-          << dendl;
-  if (cct->_conf->osd_disk_thread_ioprio_class.empty() ||
-      cct->_conf->osd_disk_thread_ioprio_priority < 0)
-    return;
-  int cls =
-    ceph_ioprio_string_to_class(cct->_conf->osd_disk_thread_ioprio_class);
-  if (cls < 0)
-    derr << __func__ << cpp_strerror(cls) << ": "
-        << "osd_disk_thread_ioprio_class is " << cct->_conf->osd_disk_thread_ioprio_class
-        << " but only the following values are allowed: idle, be or rt" << dendl;
-  else
-    disk_tp.set_ioprio(cls, cct->_conf->osd_disk_thread_ioprio_priority);
+  if (cct->_conf->osd_object_clean_region_max_num_intervals < 0) {
+    clog->warn() << "osd_object_clean_region_max_num_intervals ("
+                 << cct->_conf->osd_object_clean_region_max_num_intervals
+                << ") is < 0";
+  }
 }
 
 // --------------------------------
@@ -9322,322 +10176,507 @@ void OSD::get_latest_osdmap()
 {
   dout(10) << __func__ << " -- start" << dendl;
 
-  C_SaferCond cond;
-  service.objecter->wait_for_latest_osdmap(&cond);
-  cond.wait();
+  boost::system::error_code ec;
+  service.objecter->wait_for_latest_osdmap(ceph::async::use_blocked[ec]);
 
   dout(10) << __func__ << " -- finish" << dendl;
 }
 
 // --------------------------------
 
-int OSD::init_op_flags(OpRequestRef& op)
-{
-  const MOSDOp *m = static_cast<const MOSDOp*>(op->get_req());
-  vector<OSDOp>::const_iterator iter;
-
-  // client flags have no bearing on whether an op is a read, write, etc.
-  op->rmw_flags = 0;
-
-  if (m->has_flag(CEPH_OSD_FLAG_RWORDERED)) {
-    op->set_force_rwordered();
-  }
+void OSD::set_perf_queries(const ConfigPayload &config_payload) {
+  const OSDConfigPayload &osd_config_payload = boost::get<OSDConfigPayload>(config_payload);
+  const std::map<OSDPerfMetricQuery, OSDPerfMetricLimits> &queries = osd_config_payload.config;
+  dout(10) << "setting " << queries.size() << " queries" << dendl;
 
-  // set bits based on op codes, called methods.
-  for (iter = m->ops.begin(); iter != m->ops.end(); ++iter) {
-    if ((iter->op.op == CEPH_OSD_OP_WATCH &&
-        iter->op.watch.op == CEPH_OSD_WATCH_OP_PING)) {
-      /* This a bit odd.  PING isn't actually a write.  It can't
-       * result in an update to the object_info.  PINGs also aren'ty
-       * resent, so there's no reason to write out a log entry
-       *
-       * However, we pipeline them behind writes, so let's force
-       * the write_ordered flag.
-       */
-      op->set_force_rwordered();
-    } else {
-      if (ceph_osd_op_mode_modify(iter->op.op))
-       op->set_write();
-    }
-    if (ceph_osd_op_mode_read(iter->op.op))
-      op->set_read();
-
-    // set READ flag if there are src_oids
-    if (iter->soid.oid.name.length())
-      op->set_read();
-
-    // set PGOP flag if there are PG ops
-    if (ceph_osd_op_type_pg(iter->op.op))
-      op->set_pg_op();
-
-    if (ceph_osd_op_mode_cache(iter->op.op))
-      op->set_cache();
-
-    // check for ec base pool
-    int64_t poolid = m->get_pg().pool();
-    const pg_pool_t *pool = osdmap->get_pg_pool(poolid);
-    if (pool && pool->is_tier()) {
-      const pg_pool_t *base_pool = osdmap->get_pg_pool(pool->tier_of);
-      if (base_pool && base_pool->require_rollback()) {
-        if ((iter->op.op != CEPH_OSD_OP_READ) &&
-            (iter->op.op != CEPH_OSD_OP_CHECKSUM) &&
-            (iter->op.op != CEPH_OSD_OP_STAT) &&
-            (iter->op.op != CEPH_OSD_OP_ISDIRTY) &&
-            (iter->op.op != CEPH_OSD_OP_UNDIRTY) &&
-            (iter->op.op != CEPH_OSD_OP_GETXATTR) &&
-            (iter->op.op != CEPH_OSD_OP_GETXATTRS) &&
-            (iter->op.op != CEPH_OSD_OP_CMPXATTR) &&
-            (iter->op.op != CEPH_OSD_OP_ASSERT_VER) &&
-            (iter->op.op != CEPH_OSD_OP_LIST_WATCHERS) &&
-            (iter->op.op != CEPH_OSD_OP_LIST_SNAPS) &&
-            (iter->op.op != CEPH_OSD_OP_SETALLOCHINT) &&
-            (iter->op.op != CEPH_OSD_OP_WRITEFULL) &&
-            (iter->op.op != CEPH_OSD_OP_ROLLBACK) &&
-            (iter->op.op != CEPH_OSD_OP_CREATE) &&
-            (iter->op.op != CEPH_OSD_OP_DELETE) &&
-            (iter->op.op != CEPH_OSD_OP_SETXATTR) &&
-            (iter->op.op != CEPH_OSD_OP_RMXATTR) &&
-            (iter->op.op != CEPH_OSD_OP_STARTSYNC) &&
-            (iter->op.op != CEPH_OSD_OP_COPY_GET) &&
-            (iter->op.op != CEPH_OSD_OP_COPY_FROM)) {
-          op->set_promote();
-        }
-      }
+  std::list<OSDPerfMetricQuery> supported_queries;
+  for (auto &it : queries) {
+    auto &query = it.first;
+    if (!query.key_descriptor.empty()) {
+      supported_queries.push_back(query);
     }
+  }
+  if (supported_queries.size() < queries.size()) {
+    dout(1) << queries.size() - supported_queries.size()
+            << " unsupported queries" << dendl;
+  }
+  {
+    std::lock_guard locker{m_perf_queries_lock};
+    m_perf_queries = supported_queries;
+    m_perf_limits = queries;
+  }
+  std::vector<PGRef> pgs;
+  _get_pgs(&pgs);
+  for (auto& pg : pgs) {
+    std::scoped_lock l{*pg};
+    pg->set_dynamic_perf_stats_queries(supported_queries);
+  }
+}
+
+MetricPayload OSD::get_perf_reports() {
+  OSDMetricPayload payload;
+  std::map<OSDPerfMetricQuery, OSDPerfMetricReport> &reports = payload.report;
+
+  std::vector<PGRef> pgs;
+  _get_pgs(&pgs);
+  DynamicPerfStats dps;
+  for (auto& pg : pgs) {
+    // m_perf_queries can be modified only in set_perf_queries by mgr client
+    // request, and it is protected by by mgr client's lock, which is held
+    // when set_perf_queries/get_perf_reports are called, so we may not hold
+    // m_perf_queries_lock here.
+    DynamicPerfStats pg_dps(m_perf_queries);
+    pg->lock();
+    pg->get_dynamic_perf_stats(&pg_dps);
+    pg->unlock();
+    dps.merge(pg_dps);
+  }
+  dps.add_to_reports(m_perf_limits, &reports);
+  dout(20) << "reports for " << reports.size() << " queries" << dendl;
 
-    switch (iter->op.op) {
-    case CEPH_OSD_OP_CALL:
-      {
-       bufferlist::iterator bp = const_cast<bufferlist&>(iter->indata).begin();
-       int is_write, is_read;
-       string cname, mname;
-       bp.copy(iter->op.cls.class_len, cname);
-       bp.copy(iter->op.cls.method_len, mname);
-
-       ClassHandler::ClassData *cls;
-       int r = class_handler->open_class(cname, &cls);
-       if (r) {
-         derr << "class " << cname << " open got " << cpp_strerror(r) << dendl;
-         if (r == -ENOENT)
-           r = -EOPNOTSUPP;
-         else if (r != -EPERM) // propagate permission errors
-           r = -EIO;
-         return r;
-       }
-       int flags = cls->get_method_flags(mname.c_str());
-       if (flags < 0) {
-         if (flags == -ENOENT)
-           r = -EOPNOTSUPP;
-         else
-           r = flags;
-         return r;
-       }
-       is_read = flags & CLS_METHOD_RD;
-       is_write = flags & CLS_METHOD_WR;
-        bool is_promote = flags & CLS_METHOD_PROMOTE;
-
-       dout(10) << "class " << cname << " method " << mname << " "
-                << "flags=" << (is_read ? "r" : "")
-                             << (is_write ? "w" : "")
-                             << (is_promote ? "p" : "")
-                 << dendl;
-       if (is_read)
-         op->set_class_read();
-       if (is_write)
-         op->set_class_write();
-        if (is_promote)
-          op->set_promote();
-        op->add_class(cname, is_read, is_write, cls->whitelisted);
-       break;
-      }
+  return payload;
+}
 
-    case CEPH_OSD_OP_WATCH:
-      // force the read bit for watch since it is depends on previous
-      // watch state (and may return early if the watch exists) or, in
-      // the case of ping, is simply a read op.
-      op->set_read();
-      // fall through
-    case CEPH_OSD_OP_NOTIFY:
-    case CEPH_OSD_OP_NOTIFY_ACK:
-      {
-        op->set_promote();
-        break;
-      }
+// =============================================================
 
-    case CEPH_OSD_OP_DELETE:
-      // if we get a delete with FAILOK we can skip handle cache. without
-      // FAILOK we still need to promote (or do something smarter) to
-      // determine whether to return ENOENT or 0.
-      if (iter == m->ops.begin() &&
-         iter->op.flags == CEPH_OSD_OP_FLAG_FAILOK) {
-       op->set_skip_handle_cache();
-      }
-      // skip promotion when proxying a delete op
-      if (m->ops.size() == 1) {
-       op->set_skip_promote();
-      }
-      break;
+#undef dout_context
+#define dout_context cct
+#undef dout_prefix
+#define dout_prefix *_dout << "osd." << osd->get_nodeid() << ":" << shard_id << "." << __func__ << " "
 
-    case CEPH_OSD_OP_CACHE_TRY_FLUSH:
-    case CEPH_OSD_OP_CACHE_FLUSH:
-    case CEPH_OSD_OP_CACHE_EVICT:
-      // If try_flush/flush/evict is the only op, can skip handle cache.
-      if (m->ops.size() == 1) {
-       op->set_skip_handle_cache();
-      }
-      break;
+void OSDShard::_attach_pg(OSDShardPGSlot *slot, PG *pg)
+{
+  dout(10) << pg->pg_id << " " << pg << dendl;
+  slot->pg = pg;
+  pg->osd_shard = this;
+  pg->pg_slot = slot;
+  osd->inc_num_pgs();
 
-    case CEPH_OSD_OP_READ:
-    case CEPH_OSD_OP_SYNC_READ:
-    case CEPH_OSD_OP_SPARSE_READ:
-    case CEPH_OSD_OP_CHECKSUM:
-    case CEPH_OSD_OP_WRITEFULL:
-      if (m->ops.size() == 1 &&
-          (iter->op.flags & CEPH_OSD_OP_FLAG_FADVISE_NOCACHE ||
-           iter->op.flags & CEPH_OSD_OP_FLAG_FADVISE_DONTNEED)) {
-        op->set_skip_promote();
-      }
-      break;
+  slot->epoch = pg->get_osdmap_epoch();
+  pg_slots_by_epoch.insert(*slot);
+}
 
-    // force promotion when pin an object in cache tier
-    case CEPH_OSD_OP_CACHE_PIN:
-      op->set_promote();
-      break;
+void OSDShard::_detach_pg(OSDShardPGSlot *slot)
+{
+  dout(10) << slot->pg->pg_id << " " << slot->pg << dendl;
+  slot->pg->osd_shard = nullptr;
+  slot->pg->pg_slot = nullptr;
+  slot->pg = nullptr;
+  osd->dec_num_pgs();
 
-    default:
-      break;
-    }
+  pg_slots_by_epoch.erase(pg_slots_by_epoch.iterator_to(*slot));
+  slot->epoch = 0;
+  if (waiting_for_min_pg_epoch) {
+    min_pg_epoch_cond.notify_all();
   }
+}
 
-  if (op->rmw_flags == 0)
-    return -EINVAL;
-
-  return 0;
+void OSDShard::update_pg_epoch(OSDShardPGSlot *slot, epoch_t e)
+{
+  std::lock_guard l(shard_lock);
+  dout(30) << "min was " << pg_slots_by_epoch.begin()->epoch
+          << " on " << pg_slots_by_epoch.begin()->pg->pg_id << dendl;
+  pg_slots_by_epoch.erase(pg_slots_by_epoch.iterator_to(*slot));
+  dout(20) << slot->pg->pg_id << " " << slot->epoch << " -> " << e << dendl;
+  slot->epoch = e;
+  pg_slots_by_epoch.insert(*slot);
+  dout(30) << "min is now " << pg_slots_by_epoch.begin()->epoch
+          << " on " << pg_slots_by_epoch.begin()->pg->pg_id << dendl;
+  if (waiting_for_min_pg_epoch) {
+    min_pg_epoch_cond.notify_all();
+  }
 }
 
-void OSD::PeeringWQ::_dequeue(list<PG*> *out) {
-  for (list<PG*>::iterator i = peering_queue.begin();
-      i != peering_queue.end() &&
-      out->size() < osd->cct->_conf->osd_peering_wq_batch_size;
-      ) {
-        if (in_use.count(*i)) {
-          ++i;
-        } else {
-          out->push_back(*i);
-          peering_queue.erase(i++);
-        }
+epoch_t OSDShard::get_min_pg_epoch()
+{
+  std::lock_guard l(shard_lock);
+  auto p = pg_slots_by_epoch.begin();
+  if (p == pg_slots_by_epoch.end()) {
+    return 0;
   }
-  in_use.insert(out->begin(), out->end());
+  return p->epoch;
 }
 
-// =============================================================
+void OSDShard::wait_min_pg_epoch(epoch_t need)
+{
+  std::unique_lock l{shard_lock};
+  ++waiting_for_min_pg_epoch;
+  min_pg_epoch_cond.wait(l, [need, this] {
+    if (pg_slots_by_epoch.empty()) {
+      return true;
+    } else if (pg_slots_by_epoch.begin()->epoch >= need) {
+      return true;
+    } else {
+      dout(10) << need << " waiting on "
+              << pg_slots_by_epoch.begin()->epoch << dendl;
+      return false;
+    }
+  });
+  --waiting_for_min_pg_epoch;
+}
 
-#undef dout_context
-#define dout_context osd->cct
-#undef dout_prefix
-#define dout_prefix *_dout << "osd." << osd->whoami << " op_wq "
+epoch_t OSDShard::get_max_waiting_epoch()
+{
+  std::lock_guard l(shard_lock);
+  epoch_t r = 0;
+  for (auto& i : pg_slots) {
+    if (!i.second->waiting_peering.empty()) {
+      r = std::max(r, i.second->waiting_peering.rbegin()->first);
+    }
+  }
+  return r;
+}
 
-void OSD::ShardedOpWQ::wake_pg_waiters(spg_t pgid)
+void OSDShard::consume_map(
+  const OSDMapRef& new_osdmap,
+  unsigned *pushes_to_free)
 {
-  uint32_t shard_index = pgid.hash_to_shard(shard_list.size());
-  auto sdata = shard_list[shard_index];
-  bool queued = false;
-  unsigned pushes_to_free = 0;
+  std::lock_guard l(shard_lock);
+  OSDMapRef old_osdmap;
   {
-    Mutex::Locker l(sdata->sdata_op_ordering_lock);
-    auto p = sdata->pg_slots.find(pgid);
-    if (p != sdata->pg_slots.end()) {
-      dout(20) << __func__ << " " << pgid
-              << " to_process " << p->second.to_process
-              << " waiting_for_pg=" << (int)p->second.waiting_for_pg << dendl;
-      for (auto i = p->second.to_process.rbegin();
-          i != p->second.to_process.rend();
-          ++i) {
-       sdata->_enqueue_front(make_pair(pgid, *i), osd->op_prio_cutoff);
+    std::lock_guard l(osdmap_lock);
+    old_osdmap = std::move(shard_osdmap);
+    shard_osdmap = new_osdmap;
+  }
+  dout(10) << new_osdmap->get_epoch()
+           << " (was " << (old_osdmap ? old_osdmap->get_epoch() : 0) << ")"
+          << dendl;
+  bool queued = false;
+
+  // check slots
+  auto p = pg_slots.begin();
+  while (p != pg_slots.end()) {
+    OSDShardPGSlot *slot = p->second.get();
+    const spg_t& pgid = p->first;
+    dout(20) << __func__ << " " << pgid << dendl;
+    if (!slot->waiting_for_split.empty()) {
+      dout(20) << __func__ << "  " << pgid
+              << " waiting for split " << slot->waiting_for_split << dendl;
+      ++p;
+      continue;
+    }
+    if (slot->waiting_for_merge_epoch > new_osdmap->get_epoch()) {
+      dout(20) << __func__ << "  " << pgid
+              << " waiting for merge by epoch " << slot->waiting_for_merge_epoch
+              << dendl;
+      ++p;
+      continue;
+    }
+    if (!slot->waiting_peering.empty()) {
+      epoch_t first = slot->waiting_peering.begin()->first;
+      if (first <= new_osdmap->get_epoch()) {
+       dout(20) << __func__ << "  " << pgid
+                << " pending_peering first epoch " << first
+                << " <= " << new_osdmap->get_epoch() << ", requeueing" << dendl;
+       _wake_pg_slot(pgid, slot);
+       queued = true;
       }
-      for (auto& q : p->second.to_process) {
-       pushes_to_free += q.get_reserved_pushes();
+      ++p;
+      continue;
+    }
+    if (!slot->waiting.empty()) {
+      if (new_osdmap->is_up_acting_osd_shard(pgid, osd->get_nodeid())) {
+       dout(20) << __func__ << "  " << pgid << " maps to us, keeping"
+                << dendl;
+       ++p;
+       continue;
+      }
+      while (!slot->waiting.empty() &&
+            slot->waiting.front().get_map_epoch() <= new_osdmap->get_epoch()) {
+       auto& qi = slot->waiting.front();
+       dout(20) << __func__ << "  " << pgid
+                << " waiting item " << qi
+                << " epoch " << qi.get_map_epoch()
+                << " <= " << new_osdmap->get_epoch()
+                << ", "
+                << (qi.get_map_epoch() < new_osdmap->get_epoch() ? "stale" :
+                    "misdirected")
+                << ", dropping" << dendl;
+        *pushes_to_free += qi.get_reserved_pushes();
+       slot->waiting.pop_front();
       }
-      p->second.to_process.clear();
-      p->second.waiting_for_pg = false;
-      ++p->second.requeue_seq;
-      queued = true;
     }
-  }
-  if (pushes_to_free > 0) {
-    osd->service.release_reserved_pushes(pushes_to_free);
+    if (slot->waiting.empty() &&
+       slot->num_running == 0 &&
+       slot->waiting_for_split.empty() &&
+       !slot->pg) {
+      dout(20) << __func__ << "  " << pgid << " empty, pruning" << dendl;
+      p = pg_slots.erase(p);
+      continue;
+    }
+
+    ++p;
   }
   if (queued) {
-    sdata->sdata_lock.Lock();
-    sdata->sdata_cond.SignalOne();
-    sdata->sdata_lock.Unlock();
+    std::lock_guard l{sdata_wait_lock};
+    sdata_cond.notify_one();
   }
 }
 
-void OSD::ShardedOpWQ::prune_pg_waiters(OSDMapRef osdmap, int whoami)
-{
-  unsigned pushes_to_free = 0;
-  for (auto sdata : shard_list) {
-    Mutex::Locker l(sdata->sdata_op_ordering_lock);
-    sdata->waiting_for_pg_osdmap = osdmap;
-    auto p = sdata->pg_slots.begin();
-    while (p != sdata->pg_slots.end()) {
-      ShardData::pg_slot& slot = p->second;
-      if (!slot.to_process.empty() && slot.num_running == 0) {
-       if (osdmap->is_up_acting_osd_shard(p->first, whoami)) {
-         dout(20) << __func__ << "  " << p->first << " maps to us, keeping"
-                  << dendl;
-         ++p;
-         continue;
-       }
-       while (!slot.to_process.empty() &&
-              slot.to_process.front().get_map_epoch() <= osdmap->get_epoch()) {
-         auto& qi = slot.to_process.front();
-         dout(20) << __func__ << "  " << p->first
-                  << " item " << qi
-                  << " epoch " << qi.get_map_epoch()
-                  << " <= " << osdmap->get_epoch()
-                  << ", stale, dropping" << dendl;
-         pushes_to_free += qi.get_reserved_pushes();
-         slot.to_process.pop_front();
-       }
+void OSDShard::_wake_pg_slot(
+  spg_t pgid,
+  OSDShardPGSlot *slot)
+{
+  dout(20) << __func__ << " " << pgid
+          << " to_process " << slot->to_process
+          << " waiting " << slot->waiting
+          << " waiting_peering " << slot->waiting_peering << dendl;
+  for (auto i = slot->to_process.rbegin();
+       i != slot->to_process.rend();
+       ++i) {
+    scheduler->enqueue_front(std::move(*i));
+  }
+  slot->to_process.clear();
+  for (auto i = slot->waiting.rbegin();
+       i != slot->waiting.rend();
+       ++i) {
+    scheduler->enqueue_front(std::move(*i));
+  }
+  slot->waiting.clear();
+  for (auto i = slot->waiting_peering.rbegin();
+       i != slot->waiting_peering.rend();
+       ++i) {
+    // this is overkill; we requeue everything, even if some of these
+    // items are waiting for maps we don't have yet.  FIXME, maybe,
+    // someday, if we decide this inefficiency matters
+    for (auto j = i->second.rbegin(); j != i->second.rend(); ++j) {
+      scheduler->enqueue_front(std::move(*j));
+    }
+  }
+  slot->waiting_peering.clear();
+  ++slot->requeue_seq;
+}
+
+void OSDShard::identify_splits_and_merges(
+  const OSDMapRef& as_of_osdmap,
+  set<pair<spg_t,epoch_t>> *split_pgs,
+  set<pair<spg_t,epoch_t>> *merge_pgs)
+{
+  std::lock_guard l(shard_lock);
+  if (shard_osdmap) {
+    for (auto& i : pg_slots) {
+      const spg_t& pgid = i.first;
+      auto *slot = i.second.get();
+      if (slot->pg) {
+       osd->service.identify_splits_and_merges(
+         shard_osdmap, as_of_osdmap, pgid,
+         split_pgs, merge_pgs);
+      } else if (!slot->waiting_for_split.empty()) {
+       osd->service.identify_splits_and_merges(
+         shard_osdmap, as_of_osdmap, pgid,
+         split_pgs, nullptr);
+      } else {
+       dout(20) << __func__ << " slot " << pgid
+                << " has no pg and waiting_for_split " << dendl;
       }
-      if (slot.to_process.empty() &&
-         slot.num_running == 0 &&
-         !slot.pg) {
-       dout(20) << __func__ << "  " << p->first << " empty, pruning" << dendl;
-       p = sdata->pg_slots.erase(p);
+    }
+  }
+}
+
+void OSDShard::prime_splits(const OSDMapRef& as_of_osdmap,
+                           set<pair<spg_t,epoch_t>> *pgids)
+{
+  std::lock_guard l(shard_lock);
+  _prime_splits(pgids);
+  if (shard_osdmap->get_epoch() > as_of_osdmap->get_epoch()) {
+    set<pair<spg_t,epoch_t>> newer_children;
+    for (auto i : *pgids) {
+      osd->service.identify_splits_and_merges(
+       as_of_osdmap, shard_osdmap, i.first,
+       &newer_children, nullptr);
+    }
+    newer_children.insert(pgids->begin(), pgids->end());
+    dout(10) << "as_of_osdmap " << as_of_osdmap->get_epoch() << " < shard "
+            << shard_osdmap->get_epoch() << ", new children " << newer_children
+            << dendl;
+    _prime_splits(&newer_children);
+    // note: we don't care what is left over here for other shards.
+    // if this shard is ahead of us and one isn't, e.g., one thread is
+    // calling into prime_splits via _process (due to a newly created
+    // pg) and this shard has a newer map due to a racing consume_map,
+    // then any grandchildren left here will be identified (or were
+    // identified) when the slower shard's osdmap is advanced.
+    // _prime_splits() will tolerate the case where the pgid is
+    // already primed.
+  }
+}
+
+void OSDShard::_prime_splits(set<pair<spg_t,epoch_t>> *pgids)
+{
+  dout(10) << *pgids << dendl;
+  auto p = pgids->begin();
+  while (p != pgids->end()) {
+    unsigned shard_index = p->first.hash_to_shard(osd->num_shards);
+    if (shard_index == shard_id) {
+      auto r = pg_slots.emplace(p->first, nullptr);
+      if (r.second) {
+       dout(10) << "priming slot " << p->first << " e" << p->second << dendl;
+       r.first->second = make_unique<OSDShardPGSlot>();
+       r.first->second->waiting_for_split.insert(p->second);
       } else {
-       ++p;
+       auto q = r.first;
+       ceph_assert(q != pg_slots.end());
+       dout(10) << "priming (existing) slot " << p->first << " e" << p->second
+                << dendl;
+       q->second->waiting_for_split.insert(p->second);
       }
+      p = pgids->erase(p);
+    } else {
+      ++p;
+    }
+  }
+}
+
+void OSDShard::prime_merges(const OSDMapRef& as_of_osdmap,
+                           set<pair<spg_t,epoch_t>> *merge_pgs)
+{
+  std::lock_guard l(shard_lock);
+  dout(20) << __func__ << " checking shard " << shard_id
+          << " for remaining merge pgs " << merge_pgs << dendl;
+  auto p = merge_pgs->begin();
+  while (p != merge_pgs->end()) {
+    spg_t pgid = p->first;
+    epoch_t epoch = p->second;
+    unsigned shard_index = pgid.hash_to_shard(osd->num_shards);
+    if (shard_index != shard_id) {
+      ++p;
+      continue;
+    }
+    OSDShardPGSlot *slot;
+    auto r = pg_slots.emplace(pgid, nullptr);
+    if (r.second) {
+      r.first->second = make_unique<OSDShardPGSlot>();
+    }
+    slot = r.first->second.get();
+    if (slot->pg) {
+      // already have pg
+      dout(20) << __func__ << "  have merge participant pg " << pgid
+              << " " << slot->pg << dendl;
+    } else if (!slot->waiting_for_split.empty() &&
+              *slot->waiting_for_split.begin() < epoch) {
+      dout(20) << __func__ << "  pending split on merge participant pg " << pgid
+              << " " << slot->waiting_for_split << dendl;
+    } else {
+      dout(20) << __func__ << "  creating empty merge participant " << pgid
+              << " for merge in " << epoch << dendl;
+      // leave history zeroed; PG::merge_from() will fill it in.
+      pg_history_t history;
+      PGCreateInfo cinfo(pgid, epoch - 1,
+                        history, PastIntervals(), false);
+      PGRef pg = osd->handle_pg_create_info(shard_osdmap, &cinfo);
+      _attach_pg(r.first->second.get(), pg.get());
+      _wake_pg_slot(pgid, slot);
+      pg->unlock();
     }
+    // mark slot for merge
+    dout(20) << __func__ << "  marking merge participant " << pgid << dendl;
+    slot->waiting_for_merge_epoch = epoch;
+    p = merge_pgs->erase(p);
   }
-  if (pushes_to_free > 0) {
-    osd->service.release_reserved_pushes(pushes_to_free);
+}
+
+void OSDShard::register_and_wake_split_child(PG *pg)
+{
+  epoch_t epoch;
+  {
+    std::lock_guard l(shard_lock);
+    dout(10) << pg->pg_id << " " << pg << dendl;
+    auto p = pg_slots.find(pg->pg_id);
+    ceph_assert(p != pg_slots.end());
+    auto *slot = p->second.get();
+    dout(20) << pg->pg_id << " waiting_for_split " << slot->waiting_for_split
+            << dendl;
+    ceph_assert(!slot->pg);
+    ceph_assert(!slot->waiting_for_split.empty());
+    _attach_pg(slot, pg);
+
+    epoch = pg->get_osdmap_epoch();
+    ceph_assert(slot->waiting_for_split.count(epoch));
+    slot->waiting_for_split.erase(epoch);
+    if (slot->waiting_for_split.empty()) {
+      _wake_pg_slot(pg->pg_id, slot);
+    } else {
+      dout(10) << __func__ << " still waiting for split on "
+              << slot->waiting_for_split << dendl;
+    }
   }
+
+  // kick child to ensure it pulls up to the latest osdmap
+  osd->enqueue_peering_evt(
+    pg->pg_id,
+    PGPeeringEventRef(
+      std::make_shared<PGPeeringEvent>(
+       epoch,
+       epoch,
+       NullEvt())));
+
+  std::lock_guard l{sdata_wait_lock};
+  sdata_cond.notify_one();
 }
 
-void OSD::ShardedOpWQ::clear_pg_pointer(spg_t pgid)
+void OSDShard::unprime_split_children(spg_t parent, unsigned old_pg_num)
 {
-  uint32_t shard_index = pgid.hash_to_shard(shard_list.size());
-  auto sdata = shard_list[shard_index];
-  Mutex::Locker l(sdata->sdata_op_ordering_lock);
-  auto p = sdata->pg_slots.find(pgid);
-  if (p != sdata->pg_slots.end()) {
-    auto& slot = p->second;
-    dout(20) << __func__ << " " << pgid << " pg " << slot.pg << dendl;
-    assert(!slot.pg || slot.pg->deleting);
-    slot.pg = nullptr;
+  std::lock_guard l(shard_lock);
+  vector<spg_t> to_delete;
+  for (auto& i : pg_slots) {
+    if (i.first != parent &&
+       i.first.get_ancestor(old_pg_num) == parent) {
+      dout(10) << __func__ << " parent " << parent << " clearing " << i.first
+              << dendl;
+      _wake_pg_slot(i.first, i.second.get());
+      to_delete.push_back(i.first);
+    }
+  }
+  for (auto pgid : to_delete) {
+    pg_slots.erase(pgid);
   }
 }
 
-void OSD::ShardedOpWQ::clear_pg_slots()
+OSDShard::OSDShard(
+  int id,
+  CephContext *cct,
+  OSD *osd)
+  : shard_id(id),
+    cct(cct),
+    osd(osd),
+    shard_name(string("OSDShard.") + stringify(id)),
+    sdata_wait_lock_name(shard_name + "::sdata_wait_lock"),
+    sdata_wait_lock{make_mutex(sdata_wait_lock_name)},
+    osdmap_lock{make_mutex(shard_name + "::osdmap_lock")},
+    shard_lock_name(shard_name + "::shard_lock"),
+    shard_lock{make_mutex(shard_lock_name)},
+    scheduler(ceph::osd::scheduler::make_scheduler(
+      cct, osd->num_shards, osd->store->is_rotational())),
+    context_queue(sdata_wait_lock, sdata_cond)
 {
-  for (auto sdata : shard_list) {
-    Mutex::Locker l(sdata->sdata_op_ordering_lock);
-    sdata->pg_slots.clear();
-    sdata->waiting_for_pg_osdmap.reset();
-    // don't bother with reserved pushes; we are shutting down
+  dout(0) << "using op scheduler " << *scheduler << dendl;
+}
+
+
+// =============================================================
+
+#undef dout_context
+#define dout_context osd->cct
+#undef dout_prefix
+#define dout_prefix *_dout << "osd." << osd->whoami << " op_wq "
+
+void OSD::ShardedOpWQ::_add_slot_waiter(
+  spg_t pgid,
+  OSDShardPGSlot *slot,
+  OpSchedulerItem&& qi)
+{
+  if (qi.is_peering()) {
+    dout(20) << __func__ << " " << pgid
+            << " peering, item epoch is "
+            << qi.get_map_epoch()
+            << ", will wait on " << qi << dendl;
+    slot->waiting_peering[qi.get_map_epoch()].push_back(std::move(qi));
+  } else {
+    dout(20) << __func__ << " " << pgid
+            << " item epoch is "
+            << qi.get_map_epoch()
+            << ", will wait on " << qi << dendl;
+    slot->waiting.push_back(std::move(qi));
   }
 }
 
@@ -9646,168 +10685,300 @@ void OSD::ShardedOpWQ::clear_pg_slots()
 
 void OSD::ShardedOpWQ::_process(uint32_t thread_index, heartbeat_handle_d *hb)
 {
-  uint32_t shard_index = thread_index % num_shards;
-  ShardData *sdata = shard_list[shard_index];
-  assert(NULL != sdata);
+  uint32_t shard_index = thread_index % osd->num_shards;
+  auto& sdata = osd->shards[shard_index];
+  ceph_assert(sdata);
+
+  // If all threads of shards do oncommits, there is a out-of-order
+  // problem.  So we choose the thread which has the smallest
+  // thread_index(thread_index < num_shards) of shard to do oncommit
+  // callback.
+  bool is_smallest_thread_index = thread_index < osd->num_shards;
 
   // peek at spg_t
-  sdata->sdata_op_ordering_lock.Lock();
-  if (sdata->pqueue->empty()) {
-    dout(20) << __func__ << " empty q, waiting" << dendl;
-    // optimistically sleep a moment; maybe another work item will come along.
-    sdata->sdata_op_ordering_lock.Unlock();
-    osd->cct->get_heartbeat_map()->reset_timeout(hb,
-      osd->cct->_conf->threadpool_default_timeout, 0);
-    sdata->sdata_lock.Lock();
-    sdata->sdata_cond.WaitInterval(sdata->sdata_lock,
-      utime_t(osd->cct->_conf->threadpool_empty_queue_max_wait, 0));
-    sdata->sdata_lock.Unlock();
-    sdata->sdata_op_ordering_lock.Lock();
-    if (sdata->pqueue->empty()) {
-      sdata->sdata_op_ordering_lock.Unlock();
+  sdata->shard_lock.lock();
+  if (sdata->scheduler->empty() &&
+      (!is_smallest_thread_index || sdata->context_queue.empty())) {
+    std::unique_lock wait_lock{sdata->sdata_wait_lock};
+    if (is_smallest_thread_index && !sdata->context_queue.empty()) {
+      // we raced with a context_queue addition, don't wait
+      wait_lock.unlock();
+    } else if (!sdata->stop_waiting) {
+      dout(20) << __func__ << " empty q, waiting" << dendl;
+      osd->cct->get_heartbeat_map()->clear_timeout(hb);
+      sdata->shard_lock.unlock();
+      sdata->sdata_cond.wait(wait_lock);
+      wait_lock.unlock();
+      sdata->shard_lock.lock();
+      if (sdata->scheduler->empty() &&
+         !(is_smallest_thread_index && !sdata->context_queue.empty())) {
+       sdata->shard_lock.unlock();
+       return;
+      }
+      // found a work item; reapply default wq timeouts
+      osd->cct->get_heartbeat_map()->reset_timeout(hb,
+        timeout_interval, suicide_interval);
+    } else {
+      dout(20) << __func__ << " need return immediately" << dendl;
+      wait_lock.unlock();
+      sdata->shard_lock.unlock();
       return;
     }
   }
-  pair<spg_t, PGQueueable> item = sdata->pqueue->dequeue();
-  if (osd->is_stopping()) {
-    sdata->sdata_op_ordering_lock.Unlock();
-    return;    // OSD shutdown, discard.
+
+  list<Context *> oncommits;
+  if (is_smallest_thread_index) {
+    sdata->context_queue.move_to(oncommits);
   }
-  PGRef pg;
-  uint64_t requeue_seq;
-  {
-    auto& slot = sdata->pg_slots[item.first];
-    dout(30) << __func__ << " " << item.first
-            << " to_process " << slot.to_process
-            << " waiting_for_pg=" << (int)slot.waiting_for_pg << dendl;
-    slot.to_process.push_back(item.second);
-    // note the requeue seq now...
-    requeue_seq = slot.requeue_seq;
-    if (slot.waiting_for_pg) {
-      // save ourselves a bit of effort
-      dout(20) << __func__ << " " << item.first << " item " << item.second
-              << " queued, waiting_for_pg" << dendl;
-      sdata->sdata_op_ordering_lock.Unlock();
+
+  WorkItem work_item;
+  while (!std::get_if<OpSchedulerItem>(&work_item)) {
+    if (sdata->scheduler->empty()) {
+      if (osd->is_stopping()) {
+        sdata->shard_lock.unlock();
+        for (auto c : oncommits) {
+          dout(10) << __func__ << " discarding in-flight oncommit " << c << dendl;
+          delete c;
+        }
+        return;    // OSD shutdown, discard.
+      }
+      sdata->shard_lock.unlock();
+      handle_oncommits(oncommits);
       return;
     }
-    pg = slot.pg;
-    dout(20) << __func__ << " " << item.first << " item " << item.second
-            << " queued" << dendl;
-    ++slot.num_running;
-  }
-  sdata->sdata_op_ordering_lock.Unlock();
 
-  osd->service.maybe_inject_dispatch_delay();
+    work_item = sdata->scheduler->dequeue();
+    if (osd->is_stopping()) {
+      sdata->shard_lock.unlock();
+      for (auto c : oncommits) {
+        dout(10) << __func__ << " discarding in-flight oncommit " << c << dendl;
+        delete c;
+      }
+      return;    // OSD shutdown, discard.
+    }
+
+    // If the work item is scheduled in the future, wait until
+    // the time returned in the dequeue response before retrying.
+    if (auto when_ready = std::get_if<double>(&work_item)) {
+      if (is_smallest_thread_index) {
+        sdata->shard_lock.unlock();
+        handle_oncommits(oncommits);
+        return;
+      }
+      std::unique_lock wait_lock{sdata->sdata_wait_lock};
+      auto future_time = ceph::real_clock::from_double(*when_ready);
+      dout(10) << __func__ << " dequeue future request at " << future_time << dendl;
+      sdata->shard_lock.unlock();
+      ++sdata->waiting_threads;
+      sdata->sdata_cond.wait_until(wait_lock, future_time);
+      --sdata->waiting_threads;
+      wait_lock.unlock();
+      sdata->shard_lock.lock();
+    }
+  } // while
+
+  // Access the stored item
+  auto item = std::move(std::get<OpSchedulerItem>(work_item));
+  if (osd->is_stopping()) {
+    sdata->shard_lock.unlock();
+    for (auto c : oncommits) {
+      dout(10) << __func__ << " discarding in-flight oncommit " << c << dendl;
+      delete c;
+    }
+    return;    // OSD shutdown, discard.
+  }
 
-  // [lookup +] lock pg (if we have it)
-  if (!pg) {
-    pg = osd->_lookup_lock_pg(item.first);
-  } else {
-    pg->lock();
+  const auto token = item.get_ordering_token();
+  auto r = sdata->pg_slots.emplace(token, nullptr);
+  if (r.second) {
+    r.first->second = make_unique<OSDShardPGSlot>();
   }
+  OSDShardPGSlot *slot = r.first->second.get();
+  dout(20) << __func__ << " " << token
+          << (r.second ? " (new)" : "")
+          << " to_process " << slot->to_process
+          << " waiting " << slot->waiting
+          << " waiting_peering " << slot->waiting_peering
+          << dendl;
+  slot->to_process.push_back(std::move(item));
+  dout(20) << __func__ << " " << slot->to_process.back()
+          << " queued" << dendl;
 
-  osd->service.maybe_inject_dispatch_delay();
+ retry_pg:
+  PGRef pg = slot->pg;
 
-  boost::optional<PGQueueable> qi;
+  // lock pg (if we have it)
+  if (pg) {
+    // note the requeue seq now...
+    uint64_t requeue_seq = slot->requeue_seq;
+    ++slot->num_running;
 
-  // we don't use a Mutex::Locker here because of the
-  // osd->service.release_reserved_pushes() call below
-  sdata->sdata_op_ordering_lock.Lock();
+    sdata->shard_lock.unlock();
+    osd->service.maybe_inject_dispatch_delay();
+    pg->lock();
+    osd->service.maybe_inject_dispatch_delay();
+    sdata->shard_lock.lock();
 
-  auto q = sdata->pg_slots.find(item.first);
-  assert(q != sdata->pg_slots.end());
-  auto& slot = q->second;
-  --slot.num_running;
+    auto q = sdata->pg_slots.find(token);
+    if (q == sdata->pg_slots.end()) {
+      // this can happen if we race with pg removal.
+      dout(20) << __func__ << " slot " << token << " no longer there" << dendl;
+      pg->unlock();
+      sdata->shard_lock.unlock();
+      handle_oncommits(oncommits);
+      return;
+    }
+    slot = q->second.get();
+    --slot->num_running;
 
-  if (slot.to_process.empty()) {
-    // raced with wake_pg_waiters or prune_pg_waiters
-    dout(20) << __func__ << " " << item.first << " nothing queued" << dendl;
-    if (pg) {
+    if (slot->to_process.empty()) {
+      // raced with _wake_pg_slot or consume_map
+      dout(20) << __func__ << " " << token
+              << " nothing queued" << dendl;
       pg->unlock();
+      sdata->shard_lock.unlock();
+      handle_oncommits(oncommits);
+      return;
     }
-    sdata->sdata_op_ordering_lock.Unlock();
-    return;
-  }
-  if (requeue_seq != slot.requeue_seq) {
-    dout(20) << __func__ << " " << item.first
-            << " requeue_seq " << slot.requeue_seq << " > our "
-            << requeue_seq << ", we raced with wake_pg_waiters"
-            << dendl;
-    if (pg) {
+    if (requeue_seq != slot->requeue_seq) {
+      dout(20) << __func__ << " " << token
+              << " requeue_seq " << slot->requeue_seq << " > our "
+              << requeue_seq << ", we raced with _wake_pg_slot"
+              << dendl;
       pg->unlock();
+      sdata->shard_lock.unlock();
+      handle_oncommits(oncommits);
+      return;
     }
-    sdata->sdata_op_ordering_lock.Unlock();
-    return;
-  }
-  if (pg && !slot.pg && !pg->deleting) {
-    dout(20) << __func__ << " " << item.first << " set pg to " << pg << dendl;
-    slot.pg = pg;
-  }
-  dout(30) << __func__ << " " << item.first << " to_process " << slot.to_process
-          << " waiting_for_pg=" << (int)slot.waiting_for_pg << dendl;
-
-  // make sure we're not already waiting for this pg
-  if (slot.waiting_for_pg) {
-    dout(20) << __func__ << " " << item.first << " item " << item.second
-            << " slot is waiting_for_pg" << dendl;
-    if (pg) {
+    if (slot->pg != pg) {
+      // this can happen if we race with pg removal.
+      dout(20) << __func__ << " slot " << token << " no longer attached to "
+              << pg << dendl;
       pg->unlock();
+      goto retry_pg;
     }
-    sdata->sdata_op_ordering_lock.Unlock();
-    return;
   }
 
+  dout(20) << __func__ << " " << token
+          << " to_process " << slot->to_process
+          << " waiting " << slot->waiting
+          << " waiting_peering " << slot->waiting_peering << dendl;
+
+  ThreadPool::TPHandle tp_handle(osd->cct, hb, timeout_interval,
+                                suicide_interval);
+
   // take next item
-  qi = slot.to_process.front();
-  slot.to_process.pop_front();
-  dout(20) << __func__ << " " << item.first << " item " << *qi
-          << " pg " << pg << dendl;
+  auto qi = std::move(slot->to_process.front());
+  slot->to_process.pop_front();
+  dout(20) << __func__ << " " << qi << " pg " << pg << dendl;
+  set<pair<spg_t,epoch_t>> new_children;
+  OSDMapRef osdmap;
 
-  if (!pg) {
+  while (!pg) {
     // should this pg shard exist on this osd in this (or a later) epoch?
-    OSDMapRef osdmap = sdata->waiting_for_pg_osdmap;
-    if (osdmap->is_up_acting_osd_shard(item.first, osd->whoami)) {
-      dout(20) << __func__ << " " << item.first
-              << " no pg, should exist, will wait" << " on " << *qi << dendl;
-      slot.to_process.push_front(*qi);
-      slot.waiting_for_pg = true;
-    } else if (qi->get_map_epoch() > osdmap->get_epoch()) {
-      dout(20) << __func__ << " " << item.first << " no pg, item epoch is "
-              << qi->get_map_epoch() << " > " << osdmap->get_epoch()
-              << ", will wait on " << *qi << dendl;
-      slot.to_process.push_front(*qi);
-      slot.waiting_for_pg = true;
+    osdmap = sdata->shard_osdmap;
+    const PGCreateInfo *create_info = qi.creates_pg();
+    if (!slot->waiting_for_split.empty()) {
+      dout(20) << __func__ << " " << token
+              << " splitting " << slot->waiting_for_split << dendl;
+      _add_slot_waiter(token, slot, std::move(qi));
+    } else if (qi.get_map_epoch() > osdmap->get_epoch()) {
+      dout(20) << __func__ << " " << token
+              << " map " << qi.get_map_epoch() << " > "
+              << osdmap->get_epoch() << dendl;
+      _add_slot_waiter(token, slot, std::move(qi));
+    } else if (qi.is_peering()) {
+      if (!qi.peering_requires_pg()) {
+       // for pg-less events, we run them under the ordering lock, since
+       // we don't have the pg lock to keep them ordered.
+       qi.run(osd, sdata, pg, tp_handle);
+      } else if (osdmap->is_up_acting_osd_shard(token, osd->whoami)) {
+       if (create_info) {
+         if (create_info->by_mon &&
+             osdmap->get_pg_acting_primary(token.pgid) != osd->whoami) {
+           dout(20) << __func__ << " " << token
+                    << " no pg, no longer primary, ignoring mon create on "
+                    << qi << dendl;
+         } else {
+           dout(20) << __func__ << " " << token
+                    << " no pg, should create on " << qi << dendl;
+           pg = osd->handle_pg_create_info(osdmap, create_info);
+           if (pg) {
+             // we created the pg! drop out and continue "normally"!
+             sdata->_attach_pg(slot, pg.get());
+             sdata->_wake_pg_slot(token, slot);
+
+             // identify split children between create epoch and shard epoch.
+             osd->service.identify_splits_and_merges(
+               pg->get_osdmap(), osdmap, pg->pg_id, &new_children, nullptr);
+             sdata->_prime_splits(&new_children);
+             // distribute remaining split children to other shards below!
+             break;
+           }
+           dout(20) << __func__ << " ignored create on " << qi << dendl;
+         }
+       } else {
+         dout(20) << __func__ << " " << token
+                  << " no pg, peering, !create, discarding " << qi << dendl;
+       }
+      } else {
+       dout(20) << __func__ << " " << token
+                << " no pg, peering, doesn't map here e" << osdmap->get_epoch()
+                << ", discarding " << qi
+                << dendl;
+      }
+    } else if (osdmap->is_up_acting_osd_shard(token, osd->whoami)) {
+      dout(20) << __func__ << " " << token
+              << " no pg, should exist e" << osdmap->get_epoch()
+              << ", will wait on " << qi << dendl;
+      _add_slot_waiter(token, slot, std::move(qi));
     } else {
-      dout(20) << __func__ << " " << item.first << " no pg, shouldn't exist,"
-              << " dropping " << *qi << dendl;
+      dout(20) << __func__ << " " << token
+              << " no pg, shouldn't exist e" << osdmap->get_epoch()
+              << ", dropping " << qi << dendl;
       // share map with client?
-      if (boost::optional<OpRequestRef> _op = qi->maybe_get_op()) {
-       Session *session = static_cast<Session *>(
-         (*_op)->get_req()->get_connection()->get_priv());
-       if (session) {
-         osd->maybe_share_map(session, *_op, sdata->waiting_for_pg_osdmap);
-         session->put();
-       }
+      if (std::optional<OpRequestRef> _op = qi.maybe_get_op()) {
+       osd->service.maybe_share_map((*_op)->get_req()->get_connection().get(),
+                                    sdata->shard_osdmap,
+                                    (*_op)->sent_epoch);
       }
-      unsigned pushes_to_free = qi->get_reserved_pushes();
+      unsigned pushes_to_free = qi.get_reserved_pushes();
       if (pushes_to_free > 0) {
-       sdata->sdata_op_ordering_lock.Unlock();
+       sdata->shard_lock.unlock();
        osd->service.release_reserved_pushes(pushes_to_free);
+       handle_oncommits(oncommits);
        return;
       }
     }
-    sdata->sdata_op_ordering_lock.Unlock();
+    sdata->shard_lock.unlock();
+    handle_oncommits(oncommits);
     return;
   }
-  sdata->sdata_op_ordering_lock.Unlock();
+  if (qi.is_peering()) {
+    OSDMapRef osdmap = sdata->shard_osdmap;
+    if (qi.get_map_epoch() > osdmap->get_epoch()) {
+      _add_slot_waiter(token, slot, std::move(qi));
+      sdata->shard_lock.unlock();
+      pg->unlock();
+      handle_oncommits(oncommits);
+      return;
+    }
+  }
+  sdata->shard_lock.unlock();
 
+  if (!new_children.empty()) {
+    for (auto shard : osd->shards) {
+      shard->prime_splits(osdmap, &new_children);
+    }
+    ceph_assert(new_children.empty());
+  }
 
   // osd_opwq_process marks the point at which an operation has been dequeued
   // and will begin to be handled by a worker thread.
   {
 #ifdef WITH_LTTNG
     osd_reqid_t reqid;
-    if (boost::optional<OpRequestRef> _op = qi->maybe_get_op()) {
+    if (std::optional<OpRequestRef> _op = qi.maybe_get_op()) {
       reqid = (*_op)->get_reqid();
     }
 #endif
@@ -9824,14 +10995,12 @@ void OSD::ShardedOpWQ::_process(uint32_t thread_index, heartbeat_handle_d *hb)
   delete f;
   *_dout << dendl;
 
-  ThreadPool::TPHandle tp_handle(osd->cct, hb, timeout_interval,
-                                suicide_interval);
-  qi->run(osd, pg, tp_handle);
+  qi.run(osd, sdata, pg, tp_handle);
 
   {
 #ifdef WITH_LTTNG
     osd_reqid_t reqid;
-    if (boost::optional<OpRequestRef> _op = qi->maybe_get_op()) {
+    if (std::optional<OpRequestRef> _op = qi.maybe_get_op()) {
       reqid = (*_op)->get_reqid();
     }
 #endif
@@ -9839,86 +11008,90 @@ void OSD::ShardedOpWQ::_process(uint32_t thread_index, heartbeat_handle_d *hb)
         reqid.name._num, reqid.tid, reqid.inc);
   }
 
-  pg->unlock();
+  handle_oncommits(oncommits);
 }
 
-void OSD::ShardedOpWQ::_enqueue(pair<spg_t, PGQueueable> item) {
+void OSD::ShardedOpWQ::_enqueue(OpSchedulerItem&& item) {
   uint32_t shard_index =
-    item.first.hash_to_shard(shard_list.size());
+    item.get_ordering_token().hash_to_shard(osd->shards.size());
 
-  ShardData* sdata = shard_list[shard_index];
+  dout(20) << __func__ << " " << item << dendl;
+
+  OSDShard* sdata = osd->shards[shard_index];
   assert (NULL != sdata);
-  unsigned priority = item.second.get_priority();
-  unsigned cost = item.second.get_cost();
-  sdata->sdata_op_ordering_lock.Lock();
-
-  dout(20) << __func__ << " " << item.first << " " << item.second << dendl;
-  if (priority >= osd->op_prio_cutoff)
-    sdata->pqueue->enqueue_strict(
-      item.second.get_owner(), priority, item);
-  else
-    sdata->pqueue->enqueue(
-      item.second.get_owner(),
-      priority, cost, item);
-  sdata->sdata_op_ordering_lock.Unlock();
 
-  sdata->sdata_lock.Lock();
-  sdata->sdata_cond.SignalOne();
-  sdata->sdata_lock.Unlock();
+  bool empty = true;
+  {
+    std::lock_guard l{sdata->shard_lock};
+    empty = sdata->scheduler->empty();
+    sdata->scheduler->enqueue(std::move(item));
+  }
 
+  {
+    std::lock_guard l{sdata->sdata_wait_lock};
+    if (empty) {
+      sdata->sdata_cond.notify_all();
+    } else if (sdata->waiting_threads) {
+      sdata->sdata_cond.notify_one();
+    }
+  }
 }
 
-void OSD::ShardedOpWQ::_enqueue_front(pair<spg_t, PGQueueable> item)
+void OSD::ShardedOpWQ::_enqueue_front(OpSchedulerItem&& item)
 {
-  uint32_t shard_index = item.first.hash_to_shard(shard_list.size());
-  ShardData* sdata = shard_list[shard_index];
-  assert (NULL != sdata);
-  sdata->sdata_op_ordering_lock.Lock();
-  auto p = sdata->pg_slots.find(item.first);
-  if (p != sdata->pg_slots.end() && !p->second.to_process.empty()) {
+  auto shard_index = item.get_ordering_token().hash_to_shard(osd->shards.size());
+  auto& sdata = osd->shards[shard_index];
+  ceph_assert(sdata);
+  sdata->shard_lock.lock();
+  auto p = sdata->pg_slots.find(item.get_ordering_token());
+  if (p != sdata->pg_slots.end() &&
+      !p->second->to_process.empty()) {
     // we may be racing with _process, which has dequeued a new item
-    // from pqueue, put it on to_process, and is now busy taking the
+    // from scheduler, put it on to_process, and is now busy taking the
     // pg lock.  ensure this old requeued item is ordered before any
     // such newer item in to_process.
-    p->second.to_process.push_front(item.second);
-    item.second = p->second.to_process.back();
-    p->second.to_process.pop_back();
-    dout(20) << __func__ << " " << item.first
-            << " " << p->second.to_process.front()
-            << " shuffled w/ " << item.second << dendl;
+    p->second->to_process.push_front(std::move(item));
+    item = std::move(p->second->to_process.back());
+    p->second->to_process.pop_back();
+    dout(20) << __func__
+            << " " << p->second->to_process.front()
+            << " shuffled w/ " << item << dendl;
   } else {
-    dout(20) << __func__ << " " << item.first << " " << item.second << dendl;
+    dout(20) << __func__ << " " << item << dendl;
   }
-  sdata->_enqueue_front(item, osd->op_prio_cutoff);
-  sdata->sdata_op_ordering_lock.Unlock();
-  sdata->sdata_lock.Lock();
-  sdata->sdata_cond.SignalOne();
-  sdata->sdata_lock.Unlock();
+  sdata->scheduler->enqueue_front(std::move(item));
+  sdata->shard_lock.unlock();
+  std::lock_guard l{sdata->sdata_wait_lock};
+  sdata->sdata_cond.notify_one();
 }
 
-namespace ceph { 
-namespace osd_cmds { 
+namespace ceph::osd_cmds {
 
-int heap(CephContext& cct, cmdmap_t& cmdmap, Formatter& f, std::ostream& os)
+int heap(CephContext& cct, const cmdmap_t& cmdmap, Formatter& f,
+        std::ostream& os)
 {
   if (!ceph_using_tcmalloc()) {
         os << "could not issue heap profiler command -- not using tcmalloc!";
         return -EOPNOTSUPP;
   }
-  
+
   string cmd;
-  if (!cmd_getval(&cct, cmdmap, "heapcmd", cmd)) {
+  if (!cmd_getval(cmdmap, "heapcmd", cmd)) {
         os << "unable to get value for command \"" << cmd << "\"";
        return -EINVAL;
-   }
-  
+  }
+
   std::vector<std::string> cmd_vec;
   get_str_vec(cmd, cmd_vec);
-  
+
+  string val;
+  if (cmd_getval(cmdmap, "value", val)) {
+    cmd_vec.push_back(val);
+  }
+
   ceph_heap_profiler_handle_command(cmd_vec, os);
-  
+
   return 0;
 }
-}} // namespace ceph::osd_cmds
 
+} // namespace ceph::osd_cmds