You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by am...@apache.org on 2022/07/13 21:12:51 UTC

[trafficserver] branch master updated: Hostdb Restructure (#8953)

This is an automated email from the ASF dual-hosted git repository.

amc pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new 156528de4 Hostdb Restructure (#8953)
156528de4 is described below

commit 156528de4ce4d6bef2f2d2bc0963dadd9f122eec
Author: Alan M. Carroll <am...@apache.org>
AuthorDate: Wed Jul 13 16:12:44 2022 -0500

    Hostdb Restructure (#8953)
    
    * Rebase checkpoint for HostDB restructure.
    
    * Post rebase fixup.
    
    * Fix PreWarm
    
    * Fix nexthop test.
    
    * Timing fix for HostDB restructure (#34)
    
    The default duration of time_since_epoch() is
    std::chrono::high_resolution_clock::duration, which will not generally
    be seconds. hostDB.refcountcache->put expects the epoch count to be a
    number of seconds. This explicitly casts to seconds so we get that
    expected value.
    
    This also makes some other std::chrono time updates.
    
    Co-authored-by: bneradt <bn...@yahooinc.com>
    
    * Debug_bw updates. And clang-format fix. (#35)
    
    * Debug_bw updates. And clang-format fix.
    
    * If HostDB returns only failed parents, try serving from cache.
    
    Before this change, the parent cache logic would give up if it couldn't
    resolve the origin name for a request. This will attempt to retrieve a
    cached response if the resolution fails.
    
    Co-authored-by: Brian Neradt <br...@verizonmedia.com>
    Co-authored-by: bneradt <bn...@yahooinc.com>
    Co-authored-by: Brian Neradt <br...@gmail.com>
---
 configs/body_factory/default/Makefile.am           |    1 +
 configs/body_factory/default/connect#all_dead      |   17 +
 .../core-architecture/HostDB-Data-Layout.svg       |    3 +
 .../core-architecture/hostdb.en.rst                |  191 +++
 doc/developer-guide/core-architecture/index.en.rst |    1 +
 doc/uml/host-resolve.plantuml                      |   24 +
 example/plugins/c-api/protocol/TxnSM.c             |   12 +-
 include/ts/ts.h                                    |    6 +
 include/tscore/BufferWriter.h                      |    4 +-
 include/tscore/BufferWriterForward.h               |    7 +
 include/tscore/Diags.h                             |   11 +
 include/tscore/bwf_std_format.h                    |   16 +-
 include/tscore/ink_time.h                          |    1 +
 include/tscore/ts_file.h                           |    7 +
 iocore/dns/P_SplitDNSProcessor.h                   |   36 +-
 iocore/dns/SRV.h                                   |    1 -
 iocore/dns/SplitDNS.cc                             |    6 +-
 iocore/hostdb/HostDB.cc                            | 1593 +++++++++++---------
 iocore/hostdb/I_HostDBProcessor.h                  |  960 ++++++++----
 iocore/hostdb/P_HostDBProcessor.h                  |  283 +---
 plugins/lua/ts_lua_misc.c                          |   10 +-
 proxy/ControlMatcher.h                             |    8 +-
 proxy/ParentSelection.cc                           |   30 +-
 proxy/http/HttpConfig.cc                           |    4 +-
 proxy/http/HttpConfig.h                            |    4 +-
 proxy/http/HttpConnectionCount.cc                  |    2 +-
 proxy/http/HttpSM.cc                               |  358 ++---
 proxy/http/HttpSM.h                                |    7 +-
 proxy/http/HttpTransact.cc                         |  365 ++---
 proxy/http/HttpTransact.h                          |   77 +-
 proxy/http/PreWarmManager.cc                       |   46 +-
 proxy/http/PreWarmManager.h                        |    4 +-
 proxy/http/remap/unit-tests/nexthop_test_stubs.cc  |    6 +-
 src/traffic_server/InkAPI.cc                       |   52 +-
 src/traffic_server/InkAPITest.cc                   |    8 +-
 src/tscore/unit_tests/test_BufferWriterFormat.cc   |    2 +
 .../next_hop/strategies_ch2/strategies_ch2.test.py |    2 +
 .../proxy_protocol/proxy_serve_stale.test.py       |    2 +-
 .../tls/tls_verify_override_base.test.py           |    4 +-
 39 files changed, 2236 insertions(+), 1935 deletions(-)

diff --git a/configs/body_factory/default/Makefile.am b/configs/body_factory/default/Makefile.am
index a24d2e290..69eb6b681 100644
--- a/configs/body_factory/default/Makefile.am
+++ b/configs/body_factory/default/Makefile.am
@@ -28,6 +28,7 @@ dist_bodyfactory_DATA = \
 	connect\#dns_failed \
 	connect\#failed_connect \
 	connect\#hangup \
+	connect\#all_dead \
 	default \
 	interception\#no_host \
 	README \
diff --git a/configs/body_factory/default/connect#all_dead b/configs/body_factory/default/connect#all_dead
new file mode 100644
index 000000000..7e18a6298
--- /dev/null
+++ b/configs/body_factory/default/connect#all_dead
@@ -0,0 +1,17 @@
+<HTML>
+<HEAD>
+<TITLE>No Valid Host</TITLE>
+</HEAD>
+
+<BODY BGCOLOR="white" FGCOLOR="black">
+<H1>No Valid Host</H1>
+<HR>
+
+<FONT FACE="Helvetica,Arial"><B>
+Description: Unable to find a valid target host.
+
+The server was found but all of the addresses are marked dead and so there is
+no valid target address to which to connect. Please try again after a few minutes.
+</B></FONT>
+<HR>
+</BODY>
diff --git a/doc/developer-guide/core-architecture/HostDB-Data-Layout.svg b/doc/developer-guide/core-architecture/HostDB-Data-Layout.svg
new file mode 100644
index 000000000..9c02674a8
--- /dev/null
+++ b/doc/developer-guide/core-architecture/HostDB-Data-Layout.svg
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="741px" height="430px" viewBox="-0.5 -0.5 741 430" content="&lt;mxfile host=&quot;Electron&quot; modified=&quot;2021-06-15T18:08:38.026Z&quot; agent=&quot;5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/14.6.13 Chrome/89.0.4389.128 Electron/12.0.7 Safari/537.36&quot; etag=&quot;xr4FVvgPdRbuqDNpDpa1&quot; version=&quot;14.6.13&quot; type=&quot;device&quot;&gt;&lt;d [...]
\ No newline at end of file
diff --git a/doc/developer-guide/core-architecture/hostdb.en.rst b/doc/developer-guide/core-architecture/hostdb.en.rst
new file mode 100644
index 000000000..33eef3c25
--- /dev/null
+++ b/doc/developer-guide/core-architecture/hostdb.en.rst
@@ -0,0 +1,191 @@
+.. Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.
+
+.. include:: ../../common.defs
+
+.. highlight:: cpp
+.. default-domain:: cpp
+
+.. _developer-doc-hostdb:
+
+HostDB
+******
+
+HostDB is a cache of DNS results. It is used to increase performance by aggregating address
+resolution across transactions. HostDB also stores state information for specific IP addresses.
+
+Operation
+=========
+
+The primary operation for HostDB is to resolve a fully qualified domain name ("FQDN"). As noted each
+FQDN is associated with a single record. Each record has an array of items. When a resolution
+request is made the database is checked to see if the record is already present. If so, it is
+served. Otherwise a DNS request is made. When the nameserver replies a record is created, added
+to the database, and then returned to the requestor.
+
+Each info tracks several status values for its corresponding upstream. These are
+
+*  HTTP version
+*  Last failure time
+
+The HTTP version is tracked from responses and provides a mechanism to make intelligent guesses
+about the protocol to use to the upstream.
+
+The last failure time tracks when the last connection failure to the info occurred and doubles as
+a flag, where a value of ``TS_TIME_ZERO`` indicates a live target and any other value indicates a
+dead info.
+
+If an info is marked dead (has a non-zero last failure time) there is a "fail window" during which
+no connections are permitted. After this time the info is considered to be a "zombie". If all infos
+for a record are dead then a specific error message is generated (body factory tag
+"connect#all_dead"). Otherwise if the selected info is a zombie, a request is permitted but the
+zombie is immediately marked dead again, preventing any additional requests until either the fail
+window has passed or the single connection succeeds. A successful connection clears the last file
+time and the info becomes alive.
+
+Runtime Structure
+=================
+
+DNS results are stored in a global hash table as instances of ``HostDBRecord``. Each record stores
+the results of a single query. These records are not updated with new DNS results - instead a new
+record instance is created and replaces the previous instance in the table. The records are
+reference counted so such a replacement doesn't invalidate the old record if the latter is still
+being accessed. Some specific dynamic data is migrated from the old record to the new one, such as
+the failure status of the upstreams in the record.
+
+In each record is a variable length array of items, instances of ``HostDBInfo``, one for each
+IP address in the record. This is called the "round robin" data for historical reasons. For SRV
+records there is an additional storage area in the record that is used to store the SRV names.
+
+.. figure:: HostDB-Data-Layout.svg
+
+The round robin data is accessed by using an offset and count in the base record. For SRV records
+each record has an offset, relative to that ``HostDBInfo`` instance, for its own name in the name
+storage area.
+
+State information for the outbound connection has been moved to a refurbished ``DNSInfo`` class
+named ``ResolveInfo``. As much as possible relevant state information has been moved from the
+``HttpSM`` to this structure. This is intended for future work where the state machine deals only
+with upstream transactions and not sessions.
+
+``ResolveInfo`` may contain a reference to a HostDB record, which preserves the record even if it is
+replaced due to DNS queries in other transactions. The record is not required as the resolution
+information can be supplied directly without DNS or HostDB, e.g. a plugin sets the upstream address
+explicitly. The ``resolved_p`` flag indicates if the current information is valid and ready to be
+used or not. A result of this is there is no longer a specific holder for API provided addresses -
+the interface now puts the address in the ``ResolveInfo`` and marks it as resolved. This prevents
+further DNS / HostDB lookups and the address is used as is.
+
+The upstream port is a bit tricky and should be cleaned up. Currently value in ``srv_port``
+determines the port if set. If not, then the port in ``addr`` is used.
+
+Resolution Style
+----------------
+
+.. cpp:enum:: OS_Addr
+
+   Metadata about the source of the resolved address.'
+
+   .. cpp:enumerator:: TRY_DEFAULT
+
+      Use default resolution. This is the initial state.
+
+   .. cpp:enumerator:: TRY_HOSTDB
+
+      Use HostDB to resolve the target key.
+
+   .. cpp:enumerator:: TRY_CLIENT
+
+      Use the client supplied target address. This is used for transparent connections - the upstream
+      address is obtained from the inbound connection. May fail over to HostDB.
+
+   .. cpp:enumerator:: USE_HOSTDB
+
+      Use HostDB to resolve the target key.
+
+   .. cpp:enumerator:: USE_CLIENT
+
+      Use the client supplied target address.
+
+   .. cpp:enumerator:: USE_API
+
+      Use the address provided via the plugin API.
+
+   The parallel values for using HostDB and the client target address are to control fail over on
+   connection failure. The ``TRY_`` values can fail over to another style, but the ``USE_`` values
+   cannot. This prevents cycles of style changes by having any ``TRY_`` value fail over to a
+   ``USE_`` value, at which point it can no longer change. Note there is no ``TRY_API`` - if a
+   plugin sets the upstream address that is locked in.
+
+Issues
+======
+
+Currently if an upstream is marked down connections are still permitted, the only change is the
+number of retries. This has caused operational problems where dead systems are flooded with requests
+which, despite the timeouts, accumulate in ATS until ATS runs out of memory (there were instances of
+over 800K pending transactions). This also made it hard to bring the upstreams back online. With
+these changes requests to dead upstreams are strongly rate limited and other transactions are
+immediately terminated with a 502 response, protecting both the upstream and ATS.
+
+Future
+======
+
+There is still some work to be done in future PRs.
+
+*  The fail window and the zombie window should be separate values. It is quite reasonable to want
+   to configure a very short fail window (possibly 0) with a moderately long zombie window so that
+   probing connections can immediately start going upstream at a low rate.
+
+*  Failing an upstream should be more loosely connected to transactions. Currently there is a one
+   to one relationship where failure is defined as the failure of a specific transaction to connect.
+   There are situations where the number of connections attempts for mark a failure is should be
+   larger than the number of retries for a single transaction. For transiently busy upstreams and
+   low latency requests it can be reasonable to tune the per transaction timeout low with no retries
+   but this then risks marking down upstreams that were merely a bit slow at a given moment.
+
+*  Parallel DNS requests should be supported. This is for both cross family requests and for split
+   DNS.
+
+*  It would be nice to be able to do the probing connections to an upstream using synthetic requests
+   instead of burning actual user requests. What would be needed is a handoff from ATS to the probe
+   to indicate a particular upstream is considered down, at which point active health checks are done
+   until the upstream is once again alive, at which point this is handed off back to ATS.
+
+History
+=======
+
+This version has several major architectural changes from the previous version.
+
+*  The data is split into records and info, not handled as a variant of a single data type. This
+   provides a noticeable simplification of the code.
+
+*  Single and multiple address results are treated identically - a singleton is simply a multiple
+   of size 1. This yeilds a major simplification of the implementation.
+
+*  Connections are throttled to dead upstreams, allowing only a single connection attempt per fail
+   window timing until a connection succeeds.
+
+*  Timing information is stored in ``std::chrono`` data types instead of proprietary types.
+
+*  State information has been promoted to atomics and updates are immediate rather than scheduled.
+   This also means the data in the state machine is a reference to a shared object, not a local copy.
+   The promotion was necessary to coordinate zombie connections to dead upstreams across transactions.
+
+*  The "resolve key" is now a separate data object from the HTTP request. This is a subtle but
+   major change. The effect is requests can be routed to different upstreams without changing
+   the request. Parent selection can be greatly simplified as it become merely a matter of setting
+   the resolve key, rather than having a completely different code path.
diff --git a/doc/developer-guide/core-architecture/index.en.rst b/doc/developer-guide/core-architecture/index.en.rst
index e88e35fb7..97f59712d 100644
--- a/doc/developer-guide/core-architecture/index.en.rst
+++ b/doc/developer-guide/core-architecture/index.en.rst
@@ -26,5 +26,6 @@ Core Architecture
    :maxdepth: 1
 
    heap.en
+   hostdb.en
    rpc.en
    url_rewrite_architecture.en.rst
diff --git a/doc/uml/host-resolve.plantuml b/doc/uml/host-resolve.plantuml
new file mode 100644
index 000000000..f3c6a6091
--- /dev/null
+++ b/doc/uml/host-resolve.plantuml
@@ -0,0 +1,24 @@
+' SPDX-License-Identifier: Apache-2.0
+' Licensed under the Apache License, Version 2.0 (the "License");
+' you may not use this file except in compliance with the License.
+' You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+' Unless required by  applicable law or agreed to in writing, software distributed under the License is distributed
+' on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+' See the License for the specific language governing permissions and limitations under the License.
+
+@startuml
+
+hide empty description
+
+state HttpSM {
+  state do_http_server_open {
+  }
+}
+
+state HandleRequest #cyan
+state CallOSDNSLookup #cyan
+
+CallOSDNSLookup -> OSDNSLookup
+
+@enduml
+
diff --git a/example/plugins/c-api/protocol/TxnSM.c b/example/plugins/c-api/protocol/TxnSM.c
index 8f6ae5416..cb7f00f44 100644
--- a/example/plugins/c-api/protocol/TxnSM.c
+++ b/example/plugins/c-api/protocol/TxnSM.c
@@ -477,8 +477,6 @@ int
 state_dns_lookup(TSCont contp, TSEvent event, TSHostLookupResult host_info)
 {
   TxnSM *txn_sm = (TxnSM *)TSContDataGet(contp);
-  struct sockaddr const *q_server_addr;
-  struct sockaddr_in ip_addr;
 
   TSDebug(PLUGIN_NAME, "enter state_dns_lookup");
 
@@ -489,16 +487,16 @@ state_dns_lookup(TSCont contp, TSEvent event, TSHostLookupResult host_info)
   txn_sm->q_pending_action = NULL;
 
   /* Get the server IP from data structure TSHostLookupResult. */
-  q_server_addr = TSHostLookupResultAddrGet(host_info);
+  struct sockaddr const *sa = TSHostLookupResultAddrGet(host_info);
 
   /* Connect to the server using its IP. */
   set_handler(txn_sm->q_current_handler, (TxnSMHandler)&state_connect_to_server);
   TSAssert(txn_sm->q_pending_action == NULL);
-  TSAssert(q_server_addr->sa_family == AF_INET); /* NO IPv6 in this plugin */
+  TSAssert(sa->sa_family == AF_INET); /* NO IPv6 in this plugin */
+  struct sockaddr_in *addr = (struct sockaddr_in *)(sa);
 
-  memcpy(&ip_addr, q_server_addr, sizeof(ip_addr));
-  ip_addr.sin_port         = txn_sm->q_server_port;
-  txn_sm->q_pending_action = TSNetConnect(contp, (struct sockaddr const *)&ip_addr);
+  addr->sin_port           = txn_sm->q_server_port;
+  txn_sm->q_pending_action = TSNetConnect(contp, sa);
 
   return TS_SUCCESS;
 }
diff --git a/include/ts/ts.h b/include/ts/ts.h
index 38f1160ca..66af0983d 100644
--- a/include/ts/ts.h
+++ b/include/ts/ts.h
@@ -1950,7 +1950,13 @@ tsapi TSReturnCode TSPortDescriptorAccept(TSPortDescriptor, TSCont);
 /* --------------------------------------------------------------------------
    DNS Lookups */
 tsapi TSAction TSHostLookup(TSCont contp, const char *hostname, size_t namelen);
+/** Retrieve an address from the host lookup.
+ *
+ * @param lookup_result Result handle passed to event callback.
+ * @return A @c sockaddr with the address if successful, a @c nullptr if not.
+ */
 tsapi struct sockaddr const *TSHostLookupResultAddrGet(TSHostLookupResult lookup_result);
+
 /* TODO: Eventually, we might want something like this as well, but it requires
    support for building the HostDBInfo struct:
    tsapi void TSHostLookupResultSet(TSHttpTxn txnp, TSHostLookupResult result);
diff --git a/include/tscore/BufferWriter.h b/include/tscore/BufferWriter.h
index 34e0b6bb5..85d0a74f8 100644
--- a/include/tscore/BufferWriter.h
+++ b/include/tscore/BufferWriter.h
@@ -854,10 +854,10 @@ std::string &
 bwprintv(std::string &s, ts::TextView fmt, std::tuple<Args...> const &args)
 {
   auto len = s.size(); // remember initial size
-  size_t n = ts::FixedBufferWriter(const_cast<char *>(s.data()), s.size()).printv(fmt, std::move(args)).extent();
+  size_t n = ts::FixedBufferWriter(const_cast<char *>(s.data()), s.size()).printv(fmt, args).extent();
   s.resize(n);   // always need to resize - if shorter, must clip pre-existing text.
   if (n > len) { // dropped data, try again.
-    ts::FixedBufferWriter(const_cast<char *>(s.data()), s.size()).printv(fmt, std::move(args));
+    ts::FixedBufferWriter(const_cast<char *>(s.data()), s.size()).printv(fmt, args);
   }
   return s;
 }
diff --git a/include/tscore/BufferWriterForward.h b/include/tscore/BufferWriterForward.h
index 8da67c60b..7773486b9 100644
--- a/include/tscore/BufferWriterForward.h
+++ b/include/tscore/BufferWriterForward.h
@@ -148,4 +148,11 @@ class BWFormat;
 
 class BufferWriter;
 
+/// Storage for debug messages.
+/// If @c bwprint is used with this, the storage is reused which minimizes allocations.
+/// E.g.
+/// @code
+
+inline thread_local std::string bw_dbg;
+
 } // namespace ts
diff --git a/include/tscore/Diags.h b/include/tscore/Diags.h
index bab84d80b..d87baec41 100644
--- a/include/tscore/Diags.h
+++ b/include/tscore/Diags.h
@@ -188,6 +188,17 @@ is_dbg_ctl_enabled(DbgCtl const &ctl)
     }                                               \
   } while (false)
 
+// A BufferWriter version of Debug().
+#define Debug_bw(tag__, fmt, ...)                                                        \
+  do {                                                                                   \
+    if (unlikely(diags()->on())) {                                                       \
+      static DbgCtl Debug_bw_ctl(tag__);                                                 \
+      if (Debug_bw_ctl.ptr()->on) {                                                      \
+        DbgPrint(Debug_bw_ctl, "%s", ts::bwprint(ts::bw_dbg, fmt, __VA_ARGS__).c_str()); \
+      }                                                                                  \
+    }                                                                                    \
+  } while (false)
+
 // printf-like debug output.  First parameter must be tag (C-string literal, or otherwise
 // a constexpr returning char const pointer to null-terminated C-string).
 //
diff --git a/include/tscore/bwf_std_format.h b/include/tscore/bwf_std_format.h
index e67c858fc..cb060edd1 100644
--- a/include/tscore/bwf_std_format.h
+++ b/include/tscore/bwf_std_format.h
@@ -26,6 +26,7 @@
 #include <atomic>
 #include <array>
 #include <string_view>
+#include <chrono>
 #include "tscpp/util/TextView.h"
 #include "tscore/BufferWriterForward.h"
 
@@ -38,6 +39,20 @@ bwformat(ts::BufferWriter &w, ts::BWFSpec const &spec, atomic<T> const &v)
   return ts::bwformat(w, spec, v.load());
 }
 
+template <typename Rep, typename Period>
+ts::BufferWriter &
+bwformat(ts::BufferWriter &w, ts::BWFSpec const &spec, chrono::duration<Rep, Period> const &d)
+{
+  return bwformat(w, spec, d.count());
+}
+
+template <typename Clock, typename Duration>
+ts::BufferWriter &
+bwformat(ts::BufferWriter &w, ts::BWFSpec const &spec, chrono::time_point<Clock, Duration> const &t)
+{
+  return bwformat(w, spec, t.time_since_epoch());
+}
+
 } // end namespace std
 
 namespace ts
@@ -130,5 +145,4 @@ namespace bwf
 BufferWriter &bwformat(BufferWriter &w, BWFSpec const &spec, bwf::Errno const &e);
 BufferWriter &bwformat(BufferWriter &w, BWFSpec const &spec, bwf::Date const &date);
 BufferWriter &bwformat(BufferWriter &w, BWFSpec const &spec, bwf::OptionalAffix const &opts);
-
 } // namespace ts
diff --git a/include/tscore/ink_time.h b/include/tscore/ink_time.h
index f138bc366..1ac667c85 100644
--- a/include/tscore/ink_time.h
+++ b/include/tscore/ink_time.h
@@ -53,6 +53,7 @@ using ts_hr_time  = ts_hr_clock::time_point;
 
 using ts_seconds      = std::chrono::seconds;
 using ts_milliseconds = std::chrono::milliseconds;
+using ts_nanoseconds  = std::chrono::nanoseconds;
 
 /// Equivalent of 0 for @c ts_time. This should be used as the default initializer.
 static constexpr ts_time TS_TIME_ZERO;
diff --git a/include/tscore/ts_file.h b/include/tscore/ts_file.h
index c4389e948..8a9eff2be 100644
--- a/include/tscore/ts_file.h
+++ b/include/tscore/ts_file.h
@@ -329,5 +329,12 @@ namespace file
 
   /* ------------------------------------------------------------------- */
 } // namespace file
+
+inline BufferWriter &
+bwformat(BufferWriter &w, BWFSpec const &spec, file::path const &path)
+{
+  return bwformat(w, spec, path.string());
+}
+
 } // namespace ts
 /* ------------------------------------------------------------------- */
diff --git a/iocore/dns/P_SplitDNSProcessor.h b/iocore/dns/P_SplitDNSProcessor.h
index 5fa119e5f..7424ccfdc 100644
--- a/iocore/dns/P_SplitDNSProcessor.h
+++ b/iocore/dns/P_SplitDNSProcessor.h
@@ -79,7 +79,7 @@ struct SplitDNS : public ConfigInfo {
   SplitDNS();
   ~SplitDNS() override;
 
-  void *getDNSRecord(const char *hostname);
+  void *getDNSRecord(ts::TextView hostname);
   void findServer(RequestData *rdata, SplitDNSResult *result);
 
   DNS_table *m_DNSSrvrTable = nullptr;
@@ -116,46 +116,34 @@ SplitDNSConfig::isSplitDNSEnabled()
 class DNSRequestData : public RequestData
 {
 public:
-  DNSRequestData();
-
-  char *get_string() override;
+  DNSRequestData() = default;
 
+  char *
+  get_string() override
+  {
+    ink_release_assert(!"Do not get a writeable string from a DNS request");
+  };
   const char *get_host() override;
 
   sockaddr const *get_ip() override;        // unused required virtual method.
   sockaddr const *get_client_ip() override; // unused required virtual method.
 
-  const char *m_pHost = nullptr;
+  ts::TextView m_pHost;
 };
 
-/* --------------------------------------------------------------
-   DNSRequestData::get_string()
-   -------------------------------------------------------------- */
-TS_INLINE
-DNSRequestData::DNSRequestData() {}
-
-/* --------------------------------------------------------------
-   DNSRequestData::get_string()
-   -------------------------------------------------------------- */
-TS_INLINE char *
-DNSRequestData::get_string()
-{
-  return ats_strdup((char *)m_pHost);
-}
-
 /* --------------------------------------------------------------
    DNSRequestData::get_host()
    -------------------------------------------------------------- */
-TS_INLINE const char *
+inline const char *
 DNSRequestData::get_host()
 {
-  return m_pHost;
+  return m_pHost.data();
 }
 
 /* --------------------------------------------------------------
    DNSRequestData::get_ip()
    -------------------------------------------------------------- */
-TS_INLINE sockaddr const *
+inline sockaddr const *
 DNSRequestData::get_ip()
 {
   return nullptr;
@@ -164,7 +152,7 @@ DNSRequestData::get_ip()
 /* --------------------------------------------------------------
    DNSRequestData::get_client_ip()
    -------------------------------------------------------------- */
-TS_INLINE sockaddr const *
+inline sockaddr const *
 DNSRequestData::get_client_ip()
 {
   return nullptr;
diff --git a/iocore/dns/SRV.h b/iocore/dns/SRV.h
index ff75689e7..560223636 100644
--- a/iocore/dns/SRV.h
+++ b/iocore/dns/SRV.h
@@ -25,7 +25,6 @@
 
 #include <vector>
 #include "tscore/ink_platform.h"
-#include "I_HostDBProcessor.h"
 
 struct HostDBInfo;
 
diff --git a/iocore/dns/SplitDNS.cc b/iocore/dns/SplitDNS.cc
index 802e97981..b993f1e7f 100644
--- a/iocore/dns/SplitDNS.cc
+++ b/iocore/dns/SplitDNS.cc
@@ -178,9 +178,9 @@ SplitDNSConfig::print()
    SplitDNS::getDNSRecord()
    -------------------------------------------------------------- */
 void *
-SplitDNS::getDNSRecord(const char *hostname)
+SplitDNS::getDNSRecord(ts::TextView hostname)
 {
-  Debug("splitdns", "Called SplitDNS::getDNSRecord(%s)", hostname);
+  Debug("splitdns", "Called SplitDNS::getDNSRecord(%.*s)", int(hostname.size()), hostname.data());
 
   DNSRequestData *pRD = DNSReqAllocator.alloc();
   pRD->m_pHost        = hostname;
@@ -191,7 +191,7 @@ SplitDNS::getDNSRecord(const char *hostname)
   DNSReqAllocator.free(pRD);
 
   if (DNS_SRVR_SPECIFIED == res.r) {
-    return (void *)&(res.m_rec->m_servers);
+    return &(res.m_rec->m_servers);
   }
 
   Debug("splitdns", "Fail to match a valid splitdns rule, fallback to default dns resolver");
diff --git a/iocore/hostdb/HostDB.cc b/iocore/hostdb/HostDB.cc
index 257f35347..0095fd70f 100644
--- a/iocore/hostdb/HostDB.cc
+++ b/iocore/hostdb/HostDB.cc
@@ -26,14 +26,19 @@
 #include "P_RefCountCacheSerializer.h"
 #include "tscore/I_Layout.h"
 #include "Show.h"
-#include "tscore/Tokenizer.h"
+#include "tscore/ts_file.h"
 #include "tscore/ink_apidefs.h"
+#include "tscore/bwf_std_format.h"
 
 #include <utility>
 #include <vector>
 #include <algorithm>
 #include <random>
 #include <chrono>
+#include <http/HttpConfig.h>
+
+using ts::TextView;
+using std::chrono::duration_cast;
 
 HostDBProcessor hostDBProcessor;
 int HostDBProcessor::hostdb_strict_round_robin = 0;
@@ -50,78 +55,216 @@ unsigned int hostdb_ip_stale_interval          = HOST_DB_IP_STALE;
 unsigned int hostdb_ip_timeout_interval        = HOST_DB_IP_TIMEOUT;
 unsigned int hostdb_ip_fail_timeout_interval   = HOST_DB_IP_FAIL_TIMEOUT;
 unsigned int hostdb_serve_stale_but_revalidate = 0;
-unsigned int hostdb_hostfile_check_interval    = 86400; // 1 day
-// Epoch timestamp of the current hosts file check.
-ink_time_t hostdb_current_interval = 0;
+static ts_seconds hostdb_hostfile_check_interval{std::chrono::hours(24)};
+// Epoch timestamp of the current hosts file check. This also functions as a
+// cached version of ts_clock::now().
+ts_time hostdb_current_timestamp{TS_TIME_ZERO};
 // Epoch timestamp of the last time we actually checked for a hosts file update.
-static ink_time_t hostdb_last_interval = 0;
+static ts_time hostdb_last_timestamp{TS_TIME_ZERO};
 // Epoch timestamp when we updated the hosts file last.
-static ink_time_t hostdb_hostfile_update_timestamp = 0;
-static char hostdb_filename[PATH_NAME_MAX]         = DEFAULT_HOST_DB_FILENAME;
-int hostdb_max_count                               = DEFAULT_HOST_DB_SIZE;
-char hostdb_hostfile_path[PATH_NAME_MAX]           = "";
-int hostdb_sync_frequency                          = 0;
-int hostdb_disable_reverse_lookup                  = 0;
-int hostdb_max_iobuf_index                         = BUFFER_SIZE_INDEX_32K;
-
-// Verify the generic storage is sufficient to cover all alternate members.
-static_assert(sizeof(HostDBApplicationInfo::allotment) == sizeof(HostDBApplicationInfo),
-              "Generic storage for HostDBApplicationInfo is smaller than the union storage.");
+static ts_time hostdb_hostfile_update_timestamp{TS_TIME_ZERO};
+static char hostdb_filename[PATH_NAME_MAX] = DEFAULT_HOST_DB_FILENAME;
+int hostdb_max_count                       = DEFAULT_HOST_DB_SIZE;
+static ts::file::path hostdb_hostfile_path;
+ts_seconds hostdb_sync_frequency{0};
+int hostdb_disable_reverse_lookup = 0;
+int hostdb_max_iobuf_index        = BUFFER_SIZE_INDEX_32K;
 
 ClassAllocator<HostDBContinuation> hostDBContAllocator("hostDBContAllocator");
 
+namespace
+{
+/** Assign raw storage to an @c IpAddr
+ *
+ * @param ip Destination.
+ * @param af IP family.
+ * @param ptr Raw data for an address of family @a af.
+ */
+void
+ip_addr_set(IpAddr &ip,     ///< Target storage.
+            uint8_t af,     ///< Address format.
+            void const *ptr ///< Raw address data
+)
+{
+  if (AF_INET6 == af) {
+    ip = *static_cast<in6_addr const *>(ptr);
+  } else if (AF_INET == af) {
+    ip = *static_cast<in_addr_t const *>(ptr);
+  } else {
+    ip.invalidate();
+  }
+}
+
+unsigned int
+HOSTDB_CLIENT_IP_HASH(sockaddr const *lhs, IpAddr const &rhs)
+{
+  unsigned int zret = ~static_cast<unsigned int>(0);
+  if (lhs->sa_family == rhs.family()) {
+    if (rhs.isIp4()) {
+      in_addr_t ip1 = ats_ip4_addr_cast(lhs);
+      in_addr_t ip2 = rhs._addr._ip4;
+      zret          = (ip1 >> 16) ^ ip1 ^ ip2 ^ (ip2 >> 16);
+    } else if (rhs.isIp6()) {
+      uint32_t const *ip1 = ats_ip_addr32_cast(lhs);
+      uint32_t const *ip2 = rhs._addr._u32;
+      for (int i = 0; i < 4; ++i, ++ip1, ++ip2) {
+        zret ^= (*ip1 >> 16) ^ *ip1 ^ *ip2 ^ (*ip2 >> 16);
+      }
+    }
+  }
+  return zret & 0xFFFF;
+}
+
+} // namespace
+
+char const *
+name_of(HostDBType t)
+{
+  switch (t) {
+  case HostDBType::UNSPEC:
+    return "*";
+  case HostDBType::ADDR:
+    return "Address";
+  case HostDBType::SRV:
+    return "SRV";
+  case HostDBType::HOST:
+    return "Reverse DNS";
+  }
+  return "";
+}
+
+/** Template for creating conversions and initialization for @c std::chrono based configuration variables.
+ *
+ * @tparam V The exact type of the configuration variable.
+ *
+ * The tricky template code is to enable having a class instance for each configuration variable, instead of for each _type_ of
+ * configuration variable. This is required because the callback interface requires functions and so the actual storage must be
+ * accessible from that function. *
+ */
+template <typename V> struct ConfigDuration {
+  using self_type = ConfigDuration;
+  V *_var; ///< Pointer to the variable to control.
+
+  /** Constructor.
+   *
+   * @param v The variable to update.
+   */
+  ConfigDuration(V &v) : _var(&v) {}
+
+  /// Convert to the mgmt (configuration) type.
+  static MgmtInt
+  to_mgmt(void const *data)
+  {
+    return static_cast<MgmtInt>(static_cast<V const *>(data)->count());
+  }
+
+  /// Convert from the mgmt (configuration) type.
+  static void
+  from_mgmt(void *data, MgmtInt i)
+  {
+    *static_cast<V *>(data) = V{i};
+  }
+
+  /// The conversion structure, which handles @c MgmtInt.
+  static inline const MgmtConverter Conversions{&to_mgmt, &from_mgmt};
+
+  /** Process start up conversion from configuration.
+   *
+   * @param type The data type in the configuration.
+   * @param data The data in the configuration.
+   * @param var Pointer to the variable to update.
+   * @return @c true if @a data was successfully converted and stored, @c false if not.
+   *
+   * @note @a var is the target variable because it was explicitly set to be the value of @a _var in @c Enable.
+   */
+  static bool
+  callback(char const *, RecDataT type, RecData data, void *var)
+  {
+    if (RECD_INT == type) {
+      (*self_type::Conversions.store_int)(var, data.rec_int);
+      return true;
+    }
+    return false;
+  }
+
+  /** Enable.
+   *
+   * @param name Name of the configuration variable.
+   *
+   * This enables both reading from the configuration and handling the callback for dynamic
+   * updates of the variable.
+   */
+  void
+  Enable(std::string_view name)
+  {
+    Enable_Config_Var(name, &self_type::callback, _var);
+  }
+};
+
+ConfigDuration HostDBDownServerCacheTimeVar{HttpConfig::m_master.oride.down_server_timeout};
+// Make the conversions visible to the plugin API. This allows exporting just the conversions
+// without having to export the class definition. Again, the compiler doesn't allow doing this
+// in one line.
+extern MgmtConverter const &HostDBDownServerCacheTimeConv;
+MgmtConverter const &HostDBDownServerCacheTimeConv = HostDBDownServerCacheTimeVar.Conversions;
+
+// Not run time configurable, therefore no support beyond this class needed.
+ConfigDuration HostDBSyncFrequencyVar{hostdb_sync_frequency};
+
+void
+HostDB_Config_Init()
+{
+  HostDBDownServerCacheTimeVar.Enable("proxy.config.http.down_server.cache_time");
+  HostDBSyncFrequencyVar.Enable("proxy.config.cache.hostdb.sync_frequency");
+}
+
 // Static configuration information
 
 HostDBCache hostDB;
 
-void ParseHostFile(const char *path, unsigned int interval);
+void ParseHostFile(ts::file::path const &path, ts_seconds interval);
 
-char *
-HostDBInfo::srvname(HostDBRoundRobin *rr) const
+auto
+HostDBInfo::assign(sa_family_t af, void const *addr) -> self_type &
 {
-  if (!is_srv || !data.srv.srv_offset) {
-    return nullptr;
-  }
-  return reinterpret_cast<char *>(rr) + data.srv.srv_offset;
+  type = HostDBType::ADDR;
+  ip_addr_set(data.ip, af, addr);
+  return *this;
 }
 
-static inline bool
-is_addr_valid(uint8_t af, ///< Address family (format of data)
-              void *ptr   ///< Raw address data (not a sockaddr variant!)
-)
+auto
+HostDBInfo::assign(IpAddr const &addr) -> self_type &
 {
-  return (AF_INET == af && INADDR_ANY != *(reinterpret_cast<in_addr_t *>(ptr))) ||
-         (AF_INET6 == af && !IN6_IS_ADDR_UNSPECIFIED(reinterpret_cast<in6_addr *>(ptr)));
+  type    = HostDBType::ADDR;
+  data.ip = addr;
+  return *this;
 }
 
-static inline void
-ip_addr_set(sockaddr *ip, ///< Target storage, sockaddr compliant.
-            uint8_t af,   ///< Address format.
-            void *ptr     ///< Raw address data
-)
+auto
+HostDBInfo::assign(SRV const *srv, char const *name) -> self_type &
 {
-  if (AF_INET6 == af) {
-    ats_ip6_set(ip, *static_cast<in6_addr *>(ptr));
-  } else if (AF_INET == af) {
-    ats_ip4_set(ip, *static_cast<in_addr_t *>(ptr));
-  } else {
-    ats_ip_invalidate(ip);
-  }
+  type                  = HostDBType::SRV;
+  data.srv.srv_weight   = srv->weight;
+  data.srv.srv_priority = srv->priority;
+  data.srv.srv_port     = srv->port;
+  data.srv.key          = srv->key;
+  data.srv.srv_offset   = reinterpret_cast<char const *>(this) - name;
+  return *this;
+}
+
+char const *
+HostDBInfo::srvname() const
+{
+  return data.srv.srv_offset ? reinterpret_cast<char const *>(this) + data.srv.srv_offset : nullptr;
 }
 
-static inline void
-ip_addr_set(IpAddr &ip, ///< Target storage.
-            uint8_t af, ///< Address format.
-            void *ptr   ///< Raw address data
+static inline bool
+is_addr_valid(uint8_t af, ///< Address family (format of data)
+              void *ptr   ///< Raw address data (not a sockaddr variant!)
 )
 {
-  if (AF_INET6 == af) {
-    ip = *static_cast<in6_addr *>(ptr);
-  } else if (AF_INET == af) {
-    ip = *static_cast<in_addr_t *>(ptr);
-  } else {
-    ip.invalidate();
-  }
+  return (AF_INET == af && INADDR_ANY != *(reinterpret_cast<in_addr_t *>(ptr))) ||
+         (AF_INET6 == af && !IN6_IS_ADDR_UNSPECIFIED(reinterpret_cast<in6_addr *>(ptr)));
 }
 
 inline void
@@ -169,18 +312,12 @@ string_for(HostDBMark mark)
 static Action *register_ShowHostDB(Continuation *c, HTTPHdr *h);
 
 HostDBHash &
-HostDBHash::set_host(const char *name, int len)
+HostDBHash::set_host(TextView name)
 {
   host_name = name;
-  host_len  = len;
 
-  if (host_name && SplitDNSConfig::isSplitDNSEnabled()) {
-    const char *scan;
-    // I think this is checking for a hostname that is just an address.
-    for (scan = host_name; *scan != '\0' && (ParseRules::is_digit(*scan) || '.' == *scan || ':' == *scan); ++scan) {
-      ;
-    }
-    if ('\0' != *scan) {
+  if (!host_name.empty() && SplitDNSConfig::isSplitDNSEnabled()) {
+    if (TS_SUCCESS != ip.load(host_name)) {
       // config is released in the destructor, because we must make sure values we
       // get out of it don't evaporate while @a this is still around.
       if (!pSD) {
@@ -206,7 +343,7 @@ HostDBHash::refresh()
     const char *server_line = dns_server ? dns_server->x_dns_ip_line : nullptr;
     uint8_t m               = static_cast<uint8_t>(db_mark); // be sure of the type.
 
-    ctx.update(host_name, host_len);
+    ctx.update(host_name.data(), host_name.size());
     ctx.update(reinterpret_cast<uint8_t *>(&port), sizeof(port));
     ctx.update(&m, sizeof(m));
     if (server_line) {
@@ -235,10 +372,7 @@ HostDBHash::~HostDBHash()
   }
 }
 
-HostDBCache::HostDBCache()
-{
-  hosts_file_ptr = new RefCountedHostsFileMap();
-}
+HostDBCache::HostDBCache() {}
 
 bool
 HostDBCache::is_pending_dns_for_hash(const CryptoHash &hash)
@@ -252,6 +386,14 @@ HostDBCache::is_pending_dns_for_hash(const CryptoHash &hash)
   return false;
 }
 
+std::shared_ptr<HostFileMap>
+HostDBCache::acquire_host_file()
+{
+  std::shared_lock lock(host_file_mutex);
+  auto zret = host_file;
+  return zret;
+}
+
 HostDBCache *
 HostDBProcessor::cache()
 {
@@ -259,16 +401,16 @@ HostDBProcessor::cache()
 }
 
 struct HostDBBackgroundTask : public Continuation {
-  int frequency;
-  ink_hrtime start_time;
+  ts_seconds frequency;
+  ts_hr_time start_time;
 
   virtual int sync_event(int event, void *edata) = 0;
   int wait_event(int event, void *edata);
 
-  HostDBBackgroundTask(int frequency);
+  HostDBBackgroundTask(ts_seconds frequency);
 };
 
-HostDBBackgroundTask::HostDBBackgroundTask(int frequency) : Continuation(new_ProxyMutex()), frequency(frequency), start_time(0)
+HostDBBackgroundTask::HostDBBackgroundTask(ts_seconds frequency) : Continuation(new_ProxyMutex()), frequency(frequency)
 {
   SET_HANDLER(&HostDBBackgroundTask::sync_event);
 }
@@ -276,11 +418,11 @@ HostDBBackgroundTask::HostDBBackgroundTask(int frequency) : Continuation(new_Pro
 int
 HostDBBackgroundTask::wait_event(int, void *)
 {
-  ink_hrtime next_sync = HRTIME_SECONDS(this->frequency) - (Thread::get_hrtime() - start_time);
+  auto next_sync = this->frequency - (ts_hr_clock::now() - start_time);
 
   SET_HANDLER(&HostDBBackgroundTask::sync_event);
-  if (next_sync > HRTIME_MSECONDS(100)) {
-    eventProcessor.schedule_in(this, next_sync, ET_TASK);
+  if (next_sync > ts_milliseconds{100}) {
+    eventProcessor.schedule_in(this, duration_cast<ts_nanoseconds>(next_sync).count(), ET_TASK);
   } else {
     eventProcessor.schedule_imm(this, ET_TASK);
   }
@@ -290,16 +432,16 @@ HostDBBackgroundTask::wait_event(int, void *)
 struct HostDBSync : public HostDBBackgroundTask {
   std::string storage_path;
   std::string full_path;
-  HostDBSync(int frequency, const std::string &storage_path, const std::string &full_path)
+  HostDBSync(ts_seconds frequency, const std::string &storage_path, const std::string &full_path)
     : HostDBBackgroundTask(frequency), storage_path(std::move(storage_path)), full_path(std::move(full_path)){};
   int
   sync_event(int, void *) override
   {
     SET_HANDLER(&HostDBSync::wait_event);
-    start_time = Thread::get_hrtime();
+    start_time = ts_hr_clock::now();
 
-    new RefCountCacheSerializer<HostDBInfo>(this, hostDBProcessor.cache()->refcountcache, this->frequency, this->storage_path,
-                                            this->full_path);
+    new RefCountCacheSerializer<HostDBRecord>(this, hostDBProcessor.cache()->refcountcache, this->frequency.count(),
+                                              this->storage_path, this->full_path);
     return EVENT_DONE;
   }
 };
@@ -327,8 +469,6 @@ HostDBCache::start(int flags)
   REC_ReadConfigInteger(hostdb_max_size, "proxy.config.hostdb.max_size");
   // number of partitions
   REC_ReadConfigInt32(hostdb_partitions, "proxy.config.hostdb.partitions");
-  // how often to sync hostdb to disk
-  REC_EstablishStaticConfigInt32(hostdb_sync_frequency, "proxy.config.cache.hostdb.sync_frequency");
 
   REC_EstablishStaticConfigInt32(hostdb_max_iobuf_index, "proxy.config.hostdb.io.max_buffer_index");
 
@@ -337,13 +477,13 @@ HostDBCache::start(int flags)
   }
 
   // Setup the ref-counted cache (this must be done regardless of syncing or not).
-  this->refcountcache = new RefCountCache<HostDBInfo>(hostdb_partitions, hostdb_max_size, hostdb_max_count, HostDBInfo::version(),
-                                                      "proxy.process.hostdb.cache.");
+  this->refcountcache = new RefCountCache<HostDBRecord>(hostdb_partitions, hostdb_max_size, hostdb_max_count, HostDBRecord::Version,
+                                                        "proxy.process.hostdb.cache.");
 
   //
   // Load and sync HostDB, if we've asked for it.
   //
-  if (hostdb_sync_frequency > 0) {
+  if (hostdb_sync_frequency.count() > 0) {
     // If proxy.config.hostdb.storage_path is not set, use the local state dir. If it is set to
     // a relative path, make it relative to the prefix.
     if (storage_path[0] == '\0') {
@@ -366,7 +506,7 @@ HostDBCache::start(int flags)
 
     Debug("hostdb", "Opening %s, partitions=%d storage_size=%" PRIu64 " items=%d", full_path, hostdb_partitions, hostdb_max_size,
           hostdb_max_count);
-    int load_ret = LoadRefCountCacheFromPath<HostDBInfo>(*this->refcountcache, full_path, HostDBInfo::unmarshall);
+    int load_ret = LoadRefCountCacheFromPath<HostDBRecord>(*this->refcountcache, full_path, HostDBRecord::unmarshall);
     if (load_ret != 0) {
       Warning("Error loading cache from %s: %d", full_path, load_ret);
     }
@@ -411,13 +551,13 @@ HostDBProcessor::start(int, size_t)
   REC_EstablishStaticConfigInt32U(hostdb_ip_stale_interval, "proxy.config.hostdb.verify_after");
   REC_EstablishStaticConfigInt32U(hostdb_ip_fail_timeout_interval, "proxy.config.hostdb.fail.timeout");
   REC_EstablishStaticConfigInt32U(hostdb_serve_stale_but_revalidate, "proxy.config.hostdb.serve_stale_for");
-  REC_EstablishStaticConfigInt32U(hostdb_hostfile_check_interval, "proxy.config.hostdb.host_file.interval");
   REC_EstablishStaticConfigInt32U(hostdb_round_robin_max_count, "proxy.config.hostdb.round_robin_max_count");
 
   //
-  // Set up hostdb_current_interval
+  // Initialize hostdb_current_timestamp which is our cached version of
+  // ts_clock::now().
   //
-  hostdb_current_interval = ink_time();
+  hostdb_current_timestamp = ts_clock::now();
 
   HostDBContinuation *b = hostDBContAllocator.alloc();
   SET_CONTINUATION_HANDLER(b, (HostDBContHandler)&HostDBContinuation::backgroundEvent);
@@ -430,18 +570,14 @@ HostDBProcessor::start(int, size_t)
 void
 HostDBContinuation::init(HostDBHash const &the_hash, Options const &opt)
 {
-  hash = the_hash;
-  if (hash.host_name) {
+  hash           = the_hash;
+  hash.host_name = hash.host_name.prefix(static_cast<int>(sizeof(hash_host_name_store) - 1));
+  if (!hash.host_name.empty()) {
     // copy to backing store.
-    if (hash.host_len > static_cast<int>(sizeof(hash_host_name_store) - 1)) {
-      hash.host_len = sizeof(hash_host_name_store) - 1;
-    }
-    memcpy(hash_host_name_store, hash.host_name, hash.host_len);
-  } else {
-    hash.host_len = 0;
+    memcpy(hash_host_name_store, hash.host_name);
   }
-  hash_host_name_store[hash.host_len] = 0;
-  hash.host_name                      = hash_host_name_store;
+  hash_host_name_store[hash.host_name.size()] = 0;
+  hash.host_name.assign(hash_host_name_store, hash.host_name.size());
 
   host_res_style     = opt.host_res_style;
   dns_lookup_timeout = opt.timeout;
@@ -460,7 +596,7 @@ HostDBContinuation::refresh_hash()
 {
   Ptr<ProxyMutex> old_bucket_mutex = hostDB.refcountcache->lock_for_key(hash.hash.fold());
   // We're not pending DNS anymore.
-  remove_trigger_pending_dns();
+  remove_and_trigger_pending_dns();
   hash.refresh();
   // Update the mutex if it's from the bucket.
   // Some call sites modify this after calling @c init so need to check.
@@ -470,34 +606,22 @@ HostDBContinuation::refresh_hash()
 }
 
 static bool
-reply_to_cont(Continuation *cont, HostDBInfo *r, bool is_srv = false)
+reply_to_cont(Continuation *cont, HostDBRecord *r, bool is_srv = false)
 {
-  if (r == nullptr || r->is_srv != is_srv || r->is_failed()) {
+  if (r == nullptr || r->is_srv() != is_srv || r->is_failed()) {
     cont->handleEvent(is_srv ? EVENT_SRV_LOOKUP : EVENT_HOST_DB_LOOKUP, nullptr);
     return false;
   }
 
-  if (r->reverse_dns) {
-    if (!r->hostname()) {
+  if (r->record_type != HostDBType::HOST) {
+    if (!r->name()) {
       ink_assert(!"missing hostname");
       cont->handleEvent(is_srv ? EVENT_SRV_LOOKUP : EVENT_HOST_DB_LOOKUP, nullptr);
       Warning("bogus entry deleted from HostDB: missing hostname");
       hostDB.refcountcache->erase(r->key);
       return false;
     }
-    Debug("hostdb", "hostname = %s", r->hostname());
-  }
-
-  if (!r->is_srv && r->round_robin) {
-    if (!r->rr()) {
-      ink_assert(!"missing round-robin");
-      cont->handleEvent(is_srv ? EVENT_SRV_LOOKUP : EVENT_HOST_DB_LOOKUP, nullptr);
-      Warning("bogus entry deleted from HostDB: missing round-robin");
-      hostDB.refcountcache->erase(r->key);
-      return false;
-    }
-    ip_text_buffer ipb;
-    Debug("hostdb", "RR of %d with %d good, 1st IP = %s", r->rr()->rrcount, r->rr()->good, ats_ip_ntop(r->ip(), ipb, sizeof ipb));
+    Debug("hostdb", "hostname = %s", r->name());
   }
 
   cont->handleEvent(is_srv ? EVENT_SRV_LOOKUP : EVENT_HOST_DB_LOOKUP, r);
@@ -541,74 +665,58 @@ db_mark_for(IpAddr const &ip)
   return ip.isIp6() ? HOSTDB_MARK_IPV6 : HOSTDB_MARK_IPV4;
 }
 
-Ptr<HostDBInfo>
+HostDBRecord::Handle
 probe(const Ptr<ProxyMutex> &mutex, HostDBHash const &hash, bool ignore_timeout)
 {
+  static const Ptr<HostDBRecord> NO_RECORD;
+
   // If hostdb is disabled, don't return anything
   if (!hostdb_enable) {
-    return Ptr<HostDBInfo>();
+    return NO_RECORD;
   }
 
   // Otherwise HostDB is enabled, so we'll do our thing
   ink_assert(this_ethread() == hostDB.refcountcache->lock_for_key(hash.hash.fold())->thread_holding);
   uint64_t folded_hash = hash.hash.fold();
 
-  // get the item from cache
-  Ptr<HostDBInfo> r = hostDB.refcountcache->get(folded_hash);
+  // get the record from cache
+  Ptr<HostDBRecord> record = hostDB.refcountcache->get(folded_hash);
   // If there was nothing in the cache-- this is a miss
-  if (r.get() == nullptr) {
-    return r;
+  if (record.get() == nullptr) {
+    return record;
   }
 
   // If the dns response was failed, and we've hit the failed timeout, lets stop returning it
-  if (r->is_failed() && r->is_ip_fail_timeout()) {
-    return make_ptr((HostDBInfo *)nullptr);
-    // if we aren't ignoring timeouts, and we are past it-- then remove the item
-  } else if (!ignore_timeout && r->is_ip_timeout() && !r->serve_stale_but_revalidate()) {
+  if (record->is_failed() && record->is_ip_fail_timeout()) {
+    return NO_RECORD;
+    // if we aren't ignoring timeouts, and we are past it-- then remove the record
+  } else if (!ignore_timeout && record->is_ip_timeout() && !record->serve_stale_but_revalidate()) {
     HOSTDB_INCREMENT_DYN_STAT(hostdb_ttl_expires_stat);
-    return make_ptr((HostDBInfo *)nullptr);
+    return NO_RECORD;
   }
 
   // If the record is stale, but we want to revalidate-- lets start that up
-  if ((!ignore_timeout && r->is_ip_stale() && !r->reverse_dns) || (r->is_ip_timeout() && r->serve_stale_but_revalidate())) {
+  if ((!ignore_timeout && record->is_ip_configured_stale() && record->record_type != HostDBType::HOST) ||
+      (record->is_ip_timeout() && record->serve_stale_but_revalidate())) {
     HOSTDB_INCREMENT_DYN_STAT(hostdb_total_serve_stale_stat);
     if (hostDB.is_pending_dns_for_hash(hash.hash)) {
-      Debug("hostdb", "stale %u %u %u, using it and pending to refresh it", r->ip_interval(), r->ip_timestamp,
-            r->ip_timeout_interval);
-      return r;
-    }
-    Debug("hostdb", "stale %u %u %u, using it and refreshing it", r->ip_interval(), r->ip_timestamp, r->ip_timeout_interval);
+      Debug("hostdb", "%s",
+            ts::bwprint(ts::bw_dbg, "stale {} {} {}, using with pending refresh", record->ip_age(),
+                        record->ip_timestamp.time_since_epoch(), record->ip_timeout_interval)
+              .c_str());
+      return record;
+    }
+    Debug("hostdb", "%s",
+          ts::bwprint(ts::bw_dbg, "stale {} {} {}, using while refresh", record->ip_age(), record->ip_timestamp.time_since_epoch(),
+                      record->ip_timeout_interval)
+            .c_str());
     HostDBContinuation *c = hostDBContAllocator.alloc();
     HostDBContinuation::Options copt;
-    copt.host_res_style = host_res_style_for(r->ip());
+    copt.host_res_style = record->af_family == AF_INET6 ? HOST_RES_IPV6_ONLY : HOST_RES_IPV4_ONLY;
     c->init(hash, copt);
     c->do_dns();
   }
-  return r;
-}
-
-//
-// Insert a HostDBInfo into the database
-// A null value indicates that the block is empty.
-//
-HostDBInfo *
-HostDBContinuation::insert(unsigned int attl)
-{
-  uint64_t folded_hash = hash.hash.fold();
-
-  ink_assert(this_ethread() == hostDB.refcountcache->lock_for_key(folded_hash)->thread_holding);
-
-  HostDBInfo *r = HostDBInfo::alloc();
-  r->key        = folded_hash;
-
-  r->ip_timestamp        = hostdb_current_interval;
-  r->ip_timeout_interval = std::clamp(attl, 1u, HOST_DB_MAX_TTL);
-
-  Debug("hostdb", "inserting for: %.*s: (hash: %" PRIx64 ") now: %u timeout: %u ttl: %u", hash.host_len, hash.host_name,
-        folded_hash, r->ip_timestamp, r->ip_timeout_interval, attl);
-
-  hostDB.refcountcache->put(folded_hash, r, 0, r->expiry_time());
-  return r;
+  return record;
 }
 
 //
@@ -659,7 +767,7 @@ HostDBProcessor::getby(Continuation *cont, cb_process_result_pfn cb_process_resu
       MUTEX_TRY_LOCK(lock2, bucket_mutex, thread);
       if (lock2.is_locked()) {
         // If we can get the lock and a level 1 probe succeeds, return
-        Ptr<HostDBInfo> r = probe(bucket_mutex, hash, false);
+        HostDBRecord::Handle r = probe(bucket_mutex, hash, false);
         if (r) {
           // fail, see if we should retry with alternate
           if (hash.db_mark != HOSTDB_MARK_SRV && r->is_failed() && hash.host_name) {
@@ -668,10 +776,10 @@ HostDBProcessor::getby(Continuation *cont, cb_process_result_pfn cb_process_resu
           if (!loop) {
             // No retry -> final result. Return it.
             if (hash.db_mark == HOSTDB_MARK_SRV) {
-              Debug("hostdb", "immediate SRV answer for %.*s from hostdb", hash.host_len, hash.host_name);
-              Debug("dns_srv", "immediate SRV answer for %.*s from hostdb", hash.host_len, hash.host_name);
+              Debug("hostdb", "immediate SRV answer for %.*s from hostdb", int(hash.host_name.size()), hash.host_name.data());
+              Debug("dns_srv", "immediate SRV answer for %.*s from hostdb", int(hash.host_name.size()), hash.host_name.data());
             } else if (hash.host_name) {
-              Debug("hostdb", "immediate answer for %.*s", hash.host_len, hash.host_name);
+              Debug("hostdb", "immediate answer for %.*s", int(hash.host_name.size()), hash.host_name.data());
             } else {
               Debug("hostdb", "immediate answer for %s", hash.ip.isValid() ? hash.ip.toString(ipb, sizeof ipb) : "<null>");
             }
@@ -689,12 +797,13 @@ HostDBProcessor::getby(Continuation *cont, cb_process_result_pfn cb_process_resu
     }
   }
   if (hash.db_mark == HOSTDB_MARK_SRV) {
-    Debug("hostdb", "delaying (force=%d) SRV answer for %.*s [timeout = %d]", force_dns, hash.host_len, hash.host_name,
-          opt.timeout);
-    Debug("dns_srv", "delaying (force=%d) SRV answer for %.*s [timeout = %d]", force_dns, hash.host_len, hash.host_name,
-          opt.timeout);
+    Debug("hostdb", "delaying (force=%d) SRV answer for %.*s [timeout = %d]", force_dns, int(hash.host_name.size()),
+          hash.host_name.data(), opt.timeout);
+    Debug("dns_srv", "delaying (force=%d) SRV answer for %.*s [timeout = %d]", force_dns, int(hash.host_name.size()),
+          hash.host_name.data(), opt.timeout);
   } else if (hash.host_name) {
-    Debug("hostdb", "delaying (force=%d) answer for %.*s [timeout %d]", force_dns, hash.host_len, hash.host_name, opt.timeout);
+    Debug("hostdb", "delaying (force=%d) answer for %.*s [timeout %d]", force_dns, int(hash.host_name.size()),
+          hash.host_name.data(), opt.timeout);
   } else {
     Debug("hostdb", "delaying (force=%d) answer for %s [timeout %d]", force_dns,
           hash.ip.isValid() ? hash.ip.toString(ipb, sizeof ipb) : "<null>", opt.timeout);
@@ -727,7 +836,7 @@ HostDBProcessor::getbyname_re(Continuation *cont, const char *ahostname, int len
   ink_assert(nullptr != ahostname);
 
   // Load the hash data.
-  hash.set_host(ahostname, ahostname ? (len ? len : strlen(ahostname)) : 0);
+  hash.set_host({ahostname, ahostname ? (len ? len : strlen(ahostname)) : 0});
   // Leave hash.ip invalid
   hash.port    = 0;
   hash.db_mark = db_mark_for(opt.host_res_style);
@@ -744,7 +853,7 @@ HostDBProcessor::getbynameport_re(Continuation *cont, const char *ahostname, int
   ink_assert(nullptr != ahostname);
 
   // Load the hash data.
-  hash.set_host(ahostname, ahostname ? (len ? len : strlen(ahostname)) : 0);
+  hash.set_host({ahostname, ahostname ? (len ? len : strlen(ahostname)) : 0});
   // Leave hash.ip invalid
   hash.port    = opt.port;
   hash.db_mark = db_mark_for(opt.host_res_style);
@@ -783,7 +892,7 @@ HostDBProcessor::getSRVbyname_imm(Continuation *cont, cb_process_result_pfn proc
 
   ink_assert(nullptr != hostname);
 
-  hash.set_host(hostname, len ? len : strlen(hostname));
+  hash.set_host({hostname, len ? len : strlen(hostname)});
   // Leave hash.ip invalid
   hash.port    = 0;
   hash.db_mark = HOSTDB_MARK_SRV;
@@ -803,7 +912,7 @@ HostDBProcessor::getbyname_imm(Continuation *cont, cb_process_result_pfn process
 
   ink_assert(nullptr != hostname);
 
-  hash.set_host(hostname, len ? len : strlen(hostname));
+  hash.set_host({hostname, len ? len : strlen(hostname)});
   // Leave hash.ip invalid
   // TODO: May I rename the wrapper name to getbynameport_imm ? - oknet
   //   By comparing getbyname_re and getbynameport_re, the hash.port should be 0 if only get hostinfo by name.
@@ -838,150 +947,35 @@ HostDBProcessor::iterate(Continuation *cont)
   return &c->action;
 }
 
-static void
-do_setby(HostDBInfo *r, HostDBApplicationInfo *app, const char *hostname, IpAddr const &ip, bool is_srv = false)
-{
-  HostDBRoundRobin *rr = r->rr();
-
-  if (is_srv && (!r->is_srv || !rr)) {
-    return;
-  }
-
-  if (rr) {
-    if (is_srv) {
-      uint32_t key = makeHostHash(hostname);
-      for (int i = 0; i < rr->rrcount; i++) {
-        if (key == rr->info(i).data.srv.key && !strcmp(hostname, rr->info(i).srvname(rr))) {
-          Debug("hostdb", "immediate setby for %s", hostname);
-          rr->info(i).app.allotment.application1 = app->allotment.application1;
-          rr->info(i).app.allotment.application2 = app->allotment.application2;
-          return;
-        }
-      }
-    } else {
-      for (int i = 0; i < rr->rrcount; i++) {
-        if (rr->info(i).ip() == ip) {
-          Debug("hostdb", "immediate setby for %s", hostname ? hostname : "<addr>");
-          rr->info(i).app.allotment.application1 = app->allotment.application1;
-          rr->info(i).app.allotment.application2 = app->allotment.application2;
-          return;
-        }
-      }
-    }
-  } else {
-    if (r->reverse_dns || (!r->round_robin && ip == r->ip())) {
-      Debug("hostdb", "immediate setby for %s", hostname ? hostname : "<addr>");
-      r->app.allotment.application1 = app->allotment.application1;
-      r->app.allotment.application2 = app->allotment.application2;
-    }
-  }
-}
-
-void
-HostDBProcessor::setby(const char *hostname, int len, sockaddr const *ip, HostDBApplicationInfo *app)
-{
-  if (!hostdb_enable) {
-    return;
-  }
-
-  HostDBHash hash;
-  hash.set_host(hostname, hostname ? (len ? len : strlen(hostname)) : 0);
-  hash.ip.assign(ip);
-  hash.port    = ip ? ats_ip_port_host_order(ip) : 0;
-  hash.db_mark = db_mark_for(ip);
-  hash.refresh();
-
-  // Attempt to find the result in-line, for level 1 hits
-
-  Ptr<ProxyMutex> mutex = hostDB.refcountcache->lock_for_key(hash.hash.fold());
-  EThread *thread       = this_ethread();
-  MUTEX_TRY_LOCK(lock, mutex, thread);
-
-  if (lock.is_locked()) {
-    Ptr<HostDBInfo> r = probe(mutex, hash, false);
-    if (r) {
-      do_setby(r.get(), app, hostname, hash.ip);
-    }
-    return;
-  }
-  // Create a continuation to do a deeper probe in the background
-
-  HostDBContinuation *c = hostDBContAllocator.alloc();
-  c->init(hash);
-  c->app.allotment.application1 = app->allotment.application1;
-  c->app.allotment.application2 = app->allotment.application2;
-  SET_CONTINUATION_HANDLER(c, (HostDBContHandler)&HostDBContinuation::setbyEvent);
-  thread->schedule_in(c, MUTEX_RETRY_DELAY);
-}
-
-void
-HostDBProcessor::setby_srv(const char *hostname, int len, const char *target, HostDBApplicationInfo *app)
-{
-  if (!hostdb_enable || !hostname || !target) {
-    return;
-  }
-
-  HostDBHash hash;
-  hash.set_host(hostname, len ? len : strlen(hostname));
-  hash.port    = 0;
-  hash.db_mark = HOSTDB_MARK_SRV;
-  hash.refresh();
-
-  // Create a continuation to do a deeper probe in the background
-
-  HostDBContinuation *c = hostDBContAllocator.alloc();
-  c->init(hash);
-  ink_strlcpy(c->srv_target_name, target, MAXDNAME);
-  c->app.allotment.application1 = app->allotment.application1;
-  c->app.allotment.application2 = app->allotment.application2;
-  SET_CONTINUATION_HANDLER(c, (HostDBContHandler)&HostDBContinuation::setbyEvent);
-  eventProcessor.schedule_imm(c);
-}
-int
-HostDBContinuation::setbyEvent(int /* event ATS_UNUSED */, Event * /* e ATS_UNUSED */)
-{
-  Ptr<HostDBInfo> r = probe(mutex, hash, false);
-
-  if (r) {
-    do_setby(r.get(), &app, hash.host_name, hash.ip, is_srv());
-  }
-
-  hostdb_cont_free(this);
-  return EVENT_DONE;
-}
-
 // Lookup done, insert into the local table, return data to the
 // calling continuation.
 // NOTE: if "i" exists it means we already allocated the space etc, just return
 //
-HostDBInfo *
-HostDBContinuation::lookup_done(IpAddr const &ip, const char *aname, bool around_robin, unsigned int ttl_seconds, SRVHosts *srv,
-                                HostDBInfo *r)
+Ptr<HostDBRecord>
+HostDBContinuation::lookup_done(TextView query_name, ts_seconds answer_ttl, SRVHosts *srv, Ptr<HostDBRecord> record)
 {
   ink_assert(this_ethread() == hostDB.refcountcache->lock_for_key(hash.hash.fold())->thread_holding);
-  if (!ip.isValid() || !aname || !aname[0]) {
+  ink_assert(record);
+  if (query_name.empty()) {
     if (is_byname()) {
-      Debug("hostdb", "lookup_done() failed for '%.*s'", hash.host_len, hash.host_name);
+      Debug("hostdb", "lookup_done() failed for '%.*s'", int(hash.host_name.size()), hash.host_name.data());
     } else if (is_srv()) {
-      Debug("dns_srv", "SRV failed for '%.*s'", hash.host_len, hash.host_name);
+      Debug("dns_srv", "SRV failed for '%.*s'", int(hash.host_name.size()), hash.host_name.data());
     } else {
       ip_text_buffer b;
       Debug("hostdb", "failed for %s", hash.ip.toString(b, sizeof b));
     }
-    if (r == nullptr) {
-      r = insert(hostdb_ip_fail_timeout_interval);
-    } else {
-      r->ip_timestamp        = hostdb_current_interval;
-      r->ip_timeout_interval = std::clamp(hostdb_ip_fail_timeout_interval, 1u, HOST_DB_MAX_TTL);
-    }
+    record->ip_timestamp        = hostdb_current_timestamp;
+    record->ip_timeout_interval = ts_seconds(std::clamp(hostdb_ip_fail_timeout_interval, 1u, HOST_DB_MAX_TTL));
 
-    r->round_robin     = false;
-    r->round_robin_elt = false;
-    r->is_srv          = is_srv();
-    r->reverse_dns     = !is_byname() && !is_srv();
+    if (is_srv()) {
+      record->record_type = HostDBType::SRV;
+    } else if (!is_byname()) {
+      record->record_type = HostDBType::HOST;
+    }
 
-    r->set_failed();
-    return r;
+    record->set_failed();
+    return record;
 
   } else {
     switch (hostdb_ttl_mode) {
@@ -990,65 +984,38 @@ HostDBContinuation::lookup_done(IpAddr const &ip, const char *aname, bool around
     case TTL_OBEY:
       break;
     case TTL_IGNORE:
-      ttl_seconds = hostdb_ip_timeout_interval;
+      answer_ttl = ts_seconds(hostdb_ip_timeout_interval);
       break;
     case TTL_MIN:
-      if (hostdb_ip_timeout_interval < ttl_seconds) {
-        ttl_seconds = hostdb_ip_timeout_interval;
+      if (ts_seconds(hostdb_ip_timeout_interval) < answer_ttl) {
+        answer_ttl = ts_seconds(hostdb_ip_timeout_interval);
       }
       break;
     case TTL_MAX:
-      if (hostdb_ip_timeout_interval > ttl_seconds) {
-        ttl_seconds = hostdb_ip_timeout_interval;
+      if (ts_seconds(hostdb_ip_timeout_interval) > answer_ttl) {
+        answer_ttl = ts_seconds(hostdb_ip_timeout_interval);
       }
       break;
     }
-    HOSTDB_SUM_DYN_STAT(hostdb_ttl_stat, ttl_seconds);
+    HOSTDB_SUM_DYN_STAT(hostdb_ttl_stat, answer_ttl.count());
 
-    if (r == nullptr) {
-      r = insert(ttl_seconds);
-    } else {
-      // update the TTL
-      r->ip_timestamp        = hostdb_current_interval;
-      r->ip_timeout_interval = std::clamp(ttl_seconds, 1u, HOST_DB_MAX_TTL);
-    }
+    // update the TTL
+    record->ip_timestamp        = hostdb_current_timestamp;
+    record->ip_timeout_interval = std::clamp(answer_ttl, ts_seconds(1), ts_seconds(HOST_DB_MAX_TTL));
 
-    r->round_robin_elt = false; // only true for elements explicitly added as RR elements.
     if (is_byname()) {
-      ip_text_buffer b;
-      Debug("hostdb", "done %s TTL %d", ip.toString(b, sizeof b), ttl_seconds);
-      ats_ip_set(r->ip(), ip);
-      r->round_robin = around_robin;
-      r->reverse_dns = false;
-      if (hash.host_name != aname) {
-        ink_strlcpy(hash_host_name_store, aname, sizeof(hash_host_name_store));
-      }
-      r->is_srv = false;
+      Debug_bw("hostdb", "done {} TTL {}", hash.host_name, answer_ttl);
     } else if (is_srv()) {
-      ink_assert(srv && srv->hosts.size() && srv->hosts.size() <= hostdb_round_robin_max_count && around_robin);
-
-      r->data.srv.srv_offset = srv->hosts.size();
-      r->reverse_dns         = false;
-      r->is_srv              = true;
-      r->round_robin         = around_robin;
-
-      if (hash.host_name != aname) {
-        ink_strlcpy(hash_host_name_store, aname, sizeof(hash_host_name_store));
-      }
+      ink_assert(srv && srv->hosts.size() && srv->hosts.size() <= hostdb_round_robin_max_count);
 
+      record->record_type = HostDBType::SRV;
     } else {
-      Debug("hostdb", "done '%s' TTL %d", aname, ttl_seconds);
-      // TODO: check that this is right, it seems that the 2 hostnames are always the same
-      r->data.hostname_offset = r->hostname_offset;
-      // TODO: consolidate into a single "item type" field?
-      r->round_robin = false;
-      r->reverse_dns = true;
-      r->is_srv      = false;
+      Debug_bw("hostdb", "done {} TTL {}", hash.host_name, answer_ttl);
+      record->record_type = HostDBType::HOST;
     }
   }
 
-  ink_assert(!r->round_robin || !r->reverse_dns);
-  return r;
+  return record;
 }
 
 int
@@ -1078,28 +1045,7 @@ HostDBContinuation::dnsPendingEvent(int event, Event *e)
   }
 }
 
-// for a new HostDBInfo `r`, "inherit" from the old version of yourself if it exists in `old_rr_data`
-static int
-restore_info(HostDBInfo *r, HostDBInfo *old_r, HostDBInfo &old_info, HostDBRoundRobin *old_rr_data)
-{
-  if (old_rr_data) {
-    for (int j = 0; j < old_rr_data->rrcount; j++) {
-      if (ats_ip_addr_eq(old_rr_data->info(j).ip(), r->ip())) {
-        r->app = old_rr_data->info(j).app;
-        return true;
-      }
-    }
-  } else if (old_r) {
-    if (ats_ip_addr_eq(old_info.ip(), r->ip())) {
-      r->app = old_info.app;
-      return true;
-    }
-  }
-  return false;
-}
-
 // DNS lookup result state
-//
 int
 HostDBContinuation::dnsEvent(int event, HostEnt *e)
 {
@@ -1118,7 +1064,7 @@ HostDBContinuation::dnsEvent(int event, HostEnt *e)
         // actual DNS query. If the request rate is high enough this can cause a persistent queue where the
         // DNS query is never sent and all requests timeout, even if it was a transient error.
         // See issue #8417.
-        remove_trigger_pending_dns();
+        remove_and_trigger_pending_dns();
       } else {
         // "local" signal to give up, usually due this being one of those "other" queries.
         // That generally means @a this has already been removed from the queue, but just in case...
@@ -1144,38 +1090,25 @@ HostDBContinuation::dnsEvent(int event, HostEnt *e)
   } else {
     bool failed = !e || !e->good;
 
-    bool is_rr     = false;
     pending_action = nullptr;
 
-    if (is_srv()) {
-      is_rr = !failed && (e->srv_hosts.hosts.size() > 0);
-    } else if (!failed) {
-      is_rr = nullptr != e->ent.h_addr_list[1];
-    } else {
-    }
-
-    ttl             = failed ? 0 : e->ttl / 60;
-    int ttl_seconds = failed ? 0 : e->ttl; // ebalsa: moving to second accuracy
+    ttl = ts_seconds(failed ? 0 : e->ttl);
 
-    Ptr<HostDBInfo> old_r = probe(mutex, hash, false);
+    Ptr<HostDBRecord> old_r = probe(mutex, hash, false);
     // If the DNS lookup failed with NXDOMAIN, remove the old record
     if (e && e->isNameError() && old_r) {
       hostDB.refcountcache->erase(old_r->key);
       old_r = nullptr;
       Debug("hostdb", "Removing the old record when the DNS lookup failed with NXDOMAIN");
     }
-    HostDBInfo old_info;
-    if (old_r) {
-      old_info = *old_r.get();
-    }
-    HostDBRoundRobin *old_rr_data = old_r ? old_r->rr() : nullptr;
-    int valid_records             = 0;
-    void *first_record            = nullptr;
-    uint8_t af                    = e ? e->ent.h_addrtype : AF_UNSPEC; // address family
-    // if this is an RR response, we need to find the first record, as well as the
-    // total number of records
-    if (is_rr) {
-      if (is_srv() && !failed) {
+
+    int valid_records  = 0;
+    void *first_record = nullptr;
+    sa_family_t af     = e ? e->ent.h_addrtype : AF_UNSPEC; // address family
+
+    // Find the first record and total number of records.
+    if (!failed) {
+      if (is_srv()) {
         valid_records = e->srv_hosts.hosts.size();
       } else {
         void *ptr; // tmp for current entry.
@@ -1195,160 +1128,92 @@ HostDBContinuation::dnsEvent(int event, HostEnt *e)
 
             ++valid_records;
           } else {
-            Warning("Zero address removed from round-robin list for '%s'", hash.host_name);
+            Warning("Invalid address removed for '%.*s'", int(hash.host_name.size()), hash.host_name.data());
           }
         }
         if (!first_record) {
           failed = true;
-          is_rr  = false;
         }
       }
-    } else if (!failed) {
-      first_record = e->ent.h_addr_list[0];
-    } // else first is 0.
-
-    IpAddr tip; // temp storage if needed.
+    } // else first is nullptr
 
     // In the event that the lookup failed (SOA response-- for example) we want to use hash.host_name, since it'll be ""
-    const char *aname = (failed || strlen(hash.host_name)) ? hash.host_name : e->ent.h_name;
-
-    const size_t s_size = strlen(aname) + 1;
-    const size_t rrsize = is_rr ? HostDBRoundRobin::size(valid_records, e->srv_hosts.srv_hosts_length) : 0;
-    // where in our block of memory we are
-    int offset = sizeof(HostDBInfo);
-
-    int allocSize = s_size + rrsize; // The extra space we need for the rest of the things
-
-    HostDBInfo *r = HostDBInfo::alloc(allocSize);
-    Debug("hostdb", "allocating %d bytes for %s with %d RR records at [%p]", allocSize, aname, valid_records, r);
-    // set up the record
-    r->key = hash.hash.fold(); // always set the key
-
-    r->hostname_offset = offset;
-    ink_strlcpy(r->perm_hostname(), aname, s_size);
-    offset += s_size;
+    TextView query_name = (failed || !hash.host_name.empty()) ? hash.host_name : TextView{e->ent.h_name, strlen(e->ent.h_name)};
+    HostDBRecord::Handle r{HostDBRecord::alloc(query_name, valid_records, failed ? 0 : e->srv_hosts.srv_hosts_length)};
+    r->key              = hash.hash.fold(); // always set the key
+    r->af_family        = af;
+    r->flags.f.failed_p = failed;
 
     // If the DNS lookup failed (errors such as SERVFAIL, etc.) but we have an old record
     // which is okay with being served stale-- lets continue to serve the stale record as long as
     // the record is willing to be served.
     bool serve_stale = false;
     if (failed && old_r && old_r->serve_stale_but_revalidate()) {
-      r->free();
-      r           = old_r.get();
+      r           = old_r;
       serve_stale = true;
     } else if (is_byname()) {
-      if (first_record) {
-        ip_addr_set(tip, af, first_record);
-      }
-      r = lookup_done(tip, hash.host_name, is_rr, ttl_seconds, failed ? nullptr : &e->srv_hosts, r);
+      lookup_done(hash.host_name, ttl, failed ? nullptr : &e->srv_hosts, r);
     } else if (is_srv()) {
-      if (!failed) {
-        tip._family = AF_INET; // force the tip valid, or else the srv will fail
-      }
-      r = lookup_done(tip,            /* junk: FIXME: is the code in lookup_done() wrong to NEED this? */
-                      hash.host_name, /* hostname */
-                      is_rr,          /* is round robin, doesnt matter for SRV since we recheck getCount() inside lookup_done() */
-                      ttl_seconds,    /* ttl in seconds */
-                      failed ? nullptr : &e->srv_hosts, r);
+      lookup_done(hash.host_name, /* hostname */
+                  ttl,            /* ttl in seconds */
+                  failed ? nullptr : &e->srv_hosts, r);
     } else if (failed) {
-      r = lookup_done(tip, hash.host_name, false, ttl_seconds, nullptr, r);
+      lookup_done(hash.host_name, ttl, nullptr, r);
     } else {
-      r = lookup_done(hash.ip, e->ent.h_name, false, ttl_seconds, &e->srv_hosts, r);
+      lookup_done(e->ent.h_name, ttl, &e->srv_hosts, r);
     }
 
-    // Conditionally make rr record entries
-    if (is_rr) {
-      r->app.rr.offset = offset;
-      // This will only be set if is_rr
-      HostDBRoundRobin *rr_data = static_cast<HostDBRoundRobin *>(r->rr());
-      ;
+    if (!failed) { // implies r != old_r
+      auto rr_info = r->rr_info();
+      // Fill in record type specific data.
       if (is_srv()) {
-        int skip  = 0;
-        char *pos = reinterpret_cast<char *>(rr_data) + sizeof(HostDBRoundRobin) + valid_records * sizeof(HostDBInfo);
+        char *pos = rr_info.rebind<char>().end();
         SRV *q[valid_records];
         ink_assert(valid_records <= (int)hostdb_round_robin_max_count);
-        // sort
         for (int i = 0; i < valid_records; ++i) {
           q[i] = &e->srv_hosts.hosts[i];
         }
-        for (int i = 0; i < valid_records; ++i) {
-          for (int ii = i + 1; ii < valid_records; ++ii) {
-            if (*q[ii] < *q[i]) {
-              SRV *tmp = q[i];
-              q[i]     = q[ii];
-              q[ii]    = tmp;
-            }
-          }
-        }
-
-        rr_data->good = rr_data->rrcount = valid_records;
-        rr_data->current                 = 0;
-        for (int i = 0; i < valid_records; ++i) {
-          SRV *t                     = q[i];
-          HostDBInfo &item           = rr_data->info(i);
-          item.round_robin           = 0;
-          item.round_robin_elt       = 1;
-          item.reverse_dns           = 0;
-          item.is_srv                = 1;
-          item.data.srv.srv_weight   = t->weight;
-          item.data.srv.srv_priority = t->priority;
-          item.data.srv.srv_port     = t->port;
-          item.data.srv.key          = t->key;
-
-          ink_assert((skip + t->host_len) <= e->srv_hosts.srv_hosts_length);
-
-          memcpy(pos + skip, t->host, t->host_len);
-          item.data.srv.srv_offset = (pos - reinterpret_cast<char *>(rr_data)) + skip;
-
-          skip += t->host_len;
-
-          item.app.allotment.application1 = 0;
-          item.app.allotment.application2 = 0;
-          Debug("dns_srv", "inserted SRV RR record [%s] into HostDB with TTL: %d seconds", t->host, ttl_seconds);
-        }
-
-        // restore
-        if (old_rr_data) {
-          for (int i = 0; i < rr_data->rrcount; ++i) {
-            for (int ii = 0; ii < old_rr_data->rrcount; ++ii) {
-              if (rr_data->info(i).data.srv.key == old_rr_data->info(ii).data.srv.key) {
-                char *new_host = rr_data->info(i).srvname(rr_data);
-                char *old_host = old_rr_data->info(ii).srvname(old_rr_data);
-                if (!strcmp(new_host, old_host)) {
-                  rr_data->info(i).app = old_rr_data->info(ii).app;
-                }
+        std::sort(q, q + valid_records, [](SRV *lhs, SRV *rhs) -> bool { return *lhs < *rhs; });
+
+        SRV **cur_srv = q;
+        for (auto &item : rr_info) {
+          auto t = *cur_srv++;               // get next SRV record pointer.
+          memcpy(pos, t->host, t->host_len); // Append the name to the overall record.
+          item.assign(t, pos);
+          pos += t->host_len;
+          if (old_r) { // migrate as needed.
+            for (auto &old_item : old_r->rr_info()) {
+              if (item.data.srv.key == old_item.data.srv.key && 0 == strcmp(item.srvname(), old_item.srvname())) {
+                item.migrate_from(old_item);
+                break;
               }
             }
           }
+          // Archetypical example - "%zd" doesn't work on FreeBSD, "%ld" doesn't work on Ubuntu, "%lld" doesn't work on Fedora.
+          Debug_bw("dns_srv", "inserted SRV RR record [{}] into HostDB with TTL: {} seconds", t->host, ttl);
         }
       } else { // Otherwise this is a regular dns response
-        rr_data->good = rr_data->rrcount = valid_records;
-        rr_data->current                 = 0;
-        for (int i = 0; i < valid_records; ++i) {
-          HostDBInfo &item = rr_data->info(i);
-          ip_addr_set(item.ip(), af, e->ent.h_addr_list[i]);
-          item.round_robin     = 0;
-          item.round_robin_elt = 1;
-          item.reverse_dns     = 0;
-          item.is_srv          = 0;
-          if (!restore_info(&item, old_r.get(), old_info, old_rr_data)) {
-            item.app.allotment.application1 = 0;
-            item.app.allotment.application2 = 0;
+        unsigned idx = 0;
+        for (auto &item : rr_info) {
+          item.assign(af, e->ent.h_addr_list[idx++]);
+          if (old_r) { // migrate as needed.
+            for (auto &old_item : old_r->rr_info()) {
+              if (item.data.ip == old_item.data.ip) {
+                item.migrate_from(old_item);
+                break;
+              }
+            }
           }
         }
       }
     }
 
-    if (!failed && !is_rr && !is_srv()) {
-      restore_info(r, old_r.get(), old_info, old_rr_data);
-    }
-    ink_assert(!r || !r->round_robin || !r->reverse_dns);
-    ink_assert(failed || ((r != nullptr) && (!r->round_robin || r->app.rr.offset)));
-
-    if (!serve_stale) {
-      hostDB.refcountcache->put(hash.hash.fold(), r, allocSize, r->expiry_time());
+    if (!serve_stale) { // implies r != old_r
+      auto const duration_till_revalidate = r->expiry_time().time_since_epoch();
+      auto const seconds_till_revalidate  = duration_cast<ts_seconds>(duration_till_revalidate).count();
+      hostDB.refcountcache->put(r->key, r.get(), r->_record_size, seconds_till_revalidate);
     } else {
-      Warning("Fallback to serving stale record, skip re-update of hostdb for %s", aname);
+      Warning("Fallback to serving stale record, skip re-update of hostdb for %.*s", int(query_name.size()), query_name.data());
     }
 
     // try to callback the user
@@ -1373,7 +1238,7 @@ HostDBContinuation::dnsEvent(int event, HostEnt *e)
           if (action.continuation->mutex) {
             ink_release_assert(action.continuation->mutex == action.mutex);
           }
-          reply_to_cont(action.continuation, r, is_srv());
+          reply_to_cont(action.continuation, r.get(), is_srv());
         }
         need_to_reschedule = false;
       }
@@ -1390,7 +1255,7 @@ HostDBContinuation::dnsEvent(int event, HostEnt *e)
     hostDB.pending_dns_for_hash(hash.hash).remove(this);
 
     // wake up everyone else who is waiting
-    remove_trigger_pending_dns();
+    remove_and_trigger_pending_dns();
 
     hostdb_cont_free(this);
 
@@ -1433,7 +1298,7 @@ HostDBContinuation::iterateEvent(int event, Event *e)
 
     IntrusiveHashMap<RefCountCacheLinkage> &partMap = hostDB.refcountcache->get_partition(current_iterate_pos).get_map();
     for (const auto &it : partMap) {
-      HostDBInfo *r = static_cast<HostDBInfo *>(it.item.get());
+      auto *r = static_cast<HostDBRecord *>(it.item.get());
       if (r && !r->is_failed()) {
         action.continuation->handleEvent(EVENT_INTERVAL, static_cast<void *>(r));
       }
@@ -1499,7 +1364,7 @@ HostDBContinuation::probeEvent(int /* event ATS_UNUSED */, Event *e)
   if (!force_dns) {
     // Do the probe
     //
-    Ptr<HostDBInfo> r = probe(mutex, hash, false);
+    Ptr<HostDBRecord> r = probe(mutex, hash, false);
 
     if (r) {
       HOSTDB_INCREMENT_DYN_STAT(hostdb_total_hits_stat);
@@ -1545,7 +1410,7 @@ HostDBContinuation::set_check_pending_dns()
 }
 
 void
-HostDBContinuation::remove_trigger_pending_dns()
+HostDBContinuation::remove_and_trigger_pending_dns()
 {
   Queue<HostDBContinuation> &q = hostDB.pending_dns_for_hash(hash.hash);
   q.remove(this);
@@ -1583,31 +1448,42 @@ HostDBContinuation::do_dns()
 {
   ink_assert(!action.cancelled);
   if (is_byname()) {
-    Debug("hostdb", "DNS %s", hash.host_name);
+    Debug("hostdb", "DNS %.*s", int(hash.host_name.size()), hash.host_name.data());
     IpAddr tip;
     if (0 == tip.load(hash.host_name)) {
-      // check 127.0.0.1 format // What the heck does that mean? - AMC
+      // Need to consider if this is necessary - could the record in ResolveInfo be left null and
+      // just the resolved address set?
       if (action.continuation) {
-        HostDBInfo *r = lookup_done(tip, hash.host_name, false, HOST_DB_MAX_TTL, nullptr);
-
-        reply_to_cont(action.continuation, r);
+        HostDBRecord::Handle r{HostDBRecord::alloc(hash.host_name, 1)};
+        r->af_family = tip.family();
+        auto &info   = r->rr_info()[0];
+        info.assign(tip);
+        // tricksy - @a reply_to_cont must use an intrusive pointer to @a r if it needs to persist
+        // @a r doesn't go out of scope until after this returns. This continuation shares the mutex
+        // of the target continuation therefore this is always dispatched synchronously.
+        reply_to_cont(action.continuation, r.get());
       }
       hostdb_cont_free(this);
       return;
     }
-    ts::ConstBuffer hname(hash.host_name, hash.host_len);
-    Ptr<RefCountedHostsFileMap> current_host_file_map = hostDB.hosts_file_ptr;
-    HostsFileMap::iterator find_result                = current_host_file_map->hosts_file_map.find(hname);
-    if (find_result != current_host_file_map->hosts_file_map.end()) {
-      if (action.continuation) {
-        // Set the TTL based on how often we stat() the host file
-        HostDBInfo *r = lookup_done(IpAddr(find_result->second), hash.host_name, false, hostdb_hostfile_check_interval, nullptr);
-        reply_to_cont(action.continuation, r);
+
+    // If looking for an IPv4 or IPv6 address, check the host file.
+    if (hash.db_mark == HOSTDB_MARK_IPV6 || hash.db_mark == HOSTDB_MARK_IPV4) {
+      if (auto static_hosts = hostDB.acquire_host_file(); static_hosts) {
+        if (auto spot = static_hosts->find(hash.host_name); spot != static_hosts->end()) {
+          HostDBRecord::Handle r = (hash.db_mark == HOSTDB_MARK_IPV4) ? spot->second.record_4 : spot->second.record_6;
+          // Set the TTL based on how often we stat() the host file
+          if (r && action.continuation) {
+            r = lookup_done(hash.host_name, hostdb_hostfile_check_interval, nullptr, r);
+            reply_to_cont(action.continuation, r.get());
+            hostdb_cont_free(this);
+            return;
+          }
+        }
       }
-      hostdb_cont_free(this);
-      return;
     }
   }
+
   if (hostdb_lookup_timeout) {
     timeout = mutex->thread_holding->schedule_in(this, HRTIME_SECONDS(hostdb_lookup_timeout));
   } else {
@@ -1624,7 +1500,7 @@ HostDBContinuation::do_dns()
       }
       pending_action = dnsProcessor.gethostbyname(this, hash.host_name, opt);
     } else if (is_srv()) {
-      Debug("dns_srv", "SRV lookup of %s", hash.host_name);
+      Debug("dns_srv", "SRV lookup of %.*s", int(hash.host_name.size()), hash.host_name.data());
       pending_action = dnsProcessor.getSRVbyname(this, hash.host_name, opt);
     } else {
       ip_text_buffer ipb;
@@ -1638,50 +1514,47 @@ HostDBContinuation::do_dns()
 
 //
 // Background event
-// Just increment the current_interval.  Might do other stuff
-// here, like move records to the current position in the cluster.
-//
+// Increment the hostdb_current_timestamp which funcions as our cached version
+// of ts_clock::now().  Might do other stuff here, like move records to the
+// current position in the cluster.
 int
 HostDBContinuation::backgroundEvent(int /* event ATS_UNUSED */, Event * /* e ATS_UNUSED */)
 {
-  // No nothing if hosts file checking is not enabled.
-  if (hostdb_hostfile_check_interval == 0) {
+  std::string dbg;
+
+  hostdb_current_timestamp = ts_clock::now();
+
+  // Do nothing if hosts file checking is not enabled.
+  if (hostdb_hostfile_check_interval.count() == 0) {
     return EVENT_CONT;
   }
 
-  hostdb_current_interval = ink_time();
-
-  if ((hostdb_current_interval - hostdb_last_interval) > hostdb_hostfile_check_interval) {
+  if ((hostdb_current_timestamp - hostdb_last_timestamp) > hostdb_hostfile_check_interval) {
     bool update_p = false; // do we need to reparse the file and update?
-    struct stat info;
-    char path[sizeof(hostdb_hostfile_path)];
+    char path[PATH_NAME_MAX];
 
     REC_ReadConfigString(path, "proxy.config.hostdb.host_file.path", sizeof(path));
-    if (0 != strcasecmp(hostdb_hostfile_path, path)) {
-      Debug("hostdb", "Update host file '%s' -> '%s'", (*hostdb_hostfile_path ? hostdb_hostfile_path : "*-none-*"),
-            (*path ? path : "*-none-*"));
+    if (0 != strcasecmp(hostdb_hostfile_path.string(), path)) {
+      Debug("hostdb", "%s",
+            ts::bwprint(dbg, R"(Updating hosts file from "{}" to "{}")", hostdb_hostfile_path, ts::bwf::FirstOf(path, "")).c_str());
       // path to hostfile changed
-      hostdb_hostfile_update_timestamp = 0; // never updated from this file
-      if ('\0' != *path) {
-        memcpy(hostdb_hostfile_path, path, sizeof(hostdb_hostfile_path));
-      } else {
-        hostdb_hostfile_path[0] = 0; // mark as not there
-      }
-      update_p = true;
-    } else {
-      hostdb_last_interval = hostdb_current_interval;
-      if (*hostdb_hostfile_path) {
-        if (0 == stat(hostdb_hostfile_path, &info)) {
-          if (info.st_mtime > static_cast<time_t>(hostdb_hostfile_update_timestamp)) {
-            update_p = true; // same file but it's changed.
-          }
-        } else {
-          Debug("hostdb", "Failed to stat host file '%s'", hostdb_hostfile_path);
+      hostdb_hostfile_update_timestamp = TS_TIME_ZERO; // never updated from this file
+      hostdb_hostfile_path             = path;
+      update_p                         = true;
+    } else if (!hostdb_hostfile_path.empty()) {
+      hostdb_last_timestamp = hostdb_current_timestamp;
+      std::error_code ec;
+      auto stat{ts::file::status(hostdb_hostfile_path, ec)};
+      if (!ec) {
+        if (ts_clock::from_time_t(modification_time(stat)) > hostdb_hostfile_update_timestamp) {
+          update_p = true; // same file but it's changed.
         }
+      } else {
+        Debug("hostdb", "%s", ts::bwprint(dbg, R"(Failed to stat host file "{}" - {})", hostdb_hostfile_path, ec).c_str());
       }
     }
     if (update_p) {
-      Debug("hostdb", "Updating from host file");
+      Debug("hostdb", "%s", ts::bwprint(dbg, R"(Updating from host file "{}")", hostdb_hostfile_path).c_str());
       ParseHostFile(hostdb_hostfile_path, hostdb_hostfile_check_interval);
     }
   }
@@ -1689,37 +1562,60 @@ HostDBContinuation::backgroundEvent(int /* event ATS_UNUSED */, Event * /* e ATS
   return EVENT_CONT;
 }
 
-char *
-HostDBInfo::hostname() const
-{
-  if (!reverse_dns) {
-    return nullptr;
-  }
-
-  return (char *)this + data.hostname_offset;
-}
-
-/*
- * The perm_hostname exists for all records not just reverse dns records.
- */
-char *
-HostDBInfo::perm_hostname() const
+HostDBInfo *
+HostDBRecord::select_best_http(ts_time now, ts_seconds fail_window, sockaddr const *hash_addr)
 {
-  if (hostname_offset == 0) {
-    return nullptr;
+  ink_assert(0 < rr_count && rr_count <= hostdb_round_robin_max_count);
+
+  // @a best_any is set to a base candidate, which may be dead.
+  HostDBInfo *best_any = nullptr;
+  // @a best_alive is set when a valid target has been selected and should be used.
+  HostDBInfo *best_alive = nullptr;
+
+  auto info{this->rr_info()};
+
+  if (HostDBProcessor::hostdb_strict_round_robin) {
+    // Always select the next viable target - select failure means no valid targets at all.
+    best_alive = best_any = this->select_next_rr(now, fail_window);
+    Debug("hostdb", "Using strict round robin - index %d", this->index_of(best_alive));
+  } else if (HostDBProcessor::hostdb_timed_round_robin > 0) {
+    auto ctime = rr_ctime.load(); // cache for atomic update.
+    auto ntime = ctime + ts_seconds(HostDBProcessor::hostdb_timed_round_robin);
+    // Check and update RR if it's time - this always yields a valid target if there is one.
+    if (now > ntime && rr_ctime.compare_exchange_strong(ctime, ntime)) {
+      best_alive = best_any = this->select_next_rr(now, fail_window);
+      Debug("hostdb", "Round robin timed interval expired - index %d", this->index_of(best_alive));
+    } else { // pick the current index, which may be dead.
+      best_any = &info[this->rr_idx()];
+    }
+    Debug("hostdb", "Using timed round robin - index %d", this->index_of(best_any));
+  } else {
+    // Walk the entries and find the best (largest) hash.
+    unsigned int best_hash = 0; // any hash is better than this.
+    for (auto &target : info) {
+      unsigned int h = HOSTDB_CLIENT_IP_HASH(hash_addr, target.data.ip);
+      if (best_hash <= h) {
+        best_any  = &target;
+        best_hash = h;
+      }
+    }
+    Debug("hostdb", "Using client affinity - index %d", this->index_of(best_any));
   }
 
-  return (char *)this + hostname_offset;
-}
-
-HostDBRoundRobin *
-HostDBInfo::rr()
-{
-  if (!round_robin) {
-    return nullptr;
+  // If there is a base choice, search for valid target starting there.
+  // Otherwise there is no valid target in the record.
+  if (best_any && !best_alive) {
+    // Starting at the current target, search for a valid one.
+    for (unsigned short i = 0; i < rr_count; i++) {
+      auto target = &info[this->rr_idx(i)];
+      if (target->select(now, fail_window)) {
+        best_alive = target;
+        break;
+      }
+    }
   }
 
-  return reinterpret_cast<HostDBRoundRobin *>(reinterpret_cast<char *>(this) + this->app.rr.offset);
+  return best_alive;
 }
 
 struct ShowHostDB;
@@ -1785,41 +1681,42 @@ struct ShowHostDB : public ShowCont {
   showAllEvent(int event, Event *e)
   {
     if (event == EVENT_INTERVAL) {
-      HostDBInfo *r = reinterpret_cast<HostDBInfo *>(e);
+      auto *r = reinterpret_cast<HostDBRecord *>(e);
       if (output_json && records_seen++ > 0) {
         CHECK_SHOW(show(",")); // we need to separate records
       }
-      showOne(r, false, event, e);
-      if (r->round_robin) {
-        HostDBRoundRobin *rr_data = r->rr();
-        if (rr_data) {
-          if (!output_json) {
-            CHECK_SHOW(show("<table border=1>\n"));
-            CHECK_SHOW(show("<tr><td>%s</td><td>%d</td></tr>\n", "Total", rr_data->rrcount));
-            CHECK_SHOW(show("<tr><td>%s</td><td>%d</td></tr>\n", "Good", rr_data->good));
-            CHECK_SHOW(show("<tr><td>%s</td><td>%d</td></tr>\n", "Current", rr_data->current));
-            CHECK_SHOW(show("</table>\n"));
-          } else {
-            CHECK_SHOW(show(",\"%s\":\"%d\",", "rr_total", rr_data->rrcount));
-            CHECK_SHOW(show("\"%s\":\"%d\",", "rr_good", rr_data->good));
-            CHECK_SHOW(show("\"%s\":\"%d\",", "rr_current", rr_data->current));
-            CHECK_SHOW(show("\"rr_records\":["));
-          }
+      auto rr_info{r->rr_info()};
+      if (rr_info.count()) {
+        if (!output_json) {
+          CHECK_SHOW(show("<table border=1>\n"));
+          CHECK_SHOW(show("<tr><td>%s</td><td>%d</td></tr>\n", "Total", r->rr_count));
+          CHECK_SHOW(show("<tr><td>%s</td><td>%d</td></tr>\n", "Current", r->_rr_idx.load()));
+          CHECK_SHOW(show("<tr><td>%s</td><td>%s</td></tr>\n", "Stale", r->is_ip_configured_stale() ? "Yes" : "No"));
+          CHECK_SHOW(show("<tr><td>%s</td><td>%s</td></tr>\n", "Timed-Out", r->is_ip_timeout() ? "Yes" : "No"));
+          CHECK_SHOW(show("</table>\n"));
+        } else {
+          CHECK_SHOW(show(",\"%s\":\"%d\",", "rr_total", r->rr_count));
+          CHECK_SHOW(show("\"%s\":\"%d\",", "rr_current", r->_rr_idx.load()));
+          CHECK_SHOW(show("\"rr_records\":["));
+        }
+        CHECK_SHOW(show("<tr><td>%s</td><td>%d</td></tr>\n", "TTL", r->ip_time_remaining()));
 
-          for (int i = 0; i < rr_data->rrcount; i++) {
-            showOne(&rr_data->info(i), true, event, e, rr_data);
-            if (output_json) {
-              CHECK_SHOW(show("}")); // we need to separate records
-              if (i < (rr_data->rrcount - 1))
-                CHECK_SHOW(show(","));
+        bool need_separator = false;
+        for (auto &item : rr_info) {
+          showOne(&item, r->record_type, event, e);
+          if (output_json) {
+            CHECK_SHOW(show("}")); // we need to separate records
+            if (need_separator) {
+              CHECK_SHOW(show(","));
             }
+            need_separator = true;
           }
+        }
 
-          if (!output_json) {
-            CHECK_SHOW(show("<br />\n<br />\n"));
-          } else {
-            CHECK_SHOW(show("]"));
-          }
+        if (!output_json) {
+          CHECK_SHOW(show("<br />\n<br />\n"));
+        } else {
+          CHECK_SHOW(show("]"));
         }
       }
 
@@ -1841,67 +1738,47 @@ struct ShowHostDB : public ShowCont {
   }
 
   int
-  showOne(HostDBInfo *r, bool rr, int event, Event *e, HostDBRoundRobin *hostdb_rr = nullptr)
+  showOne(HostDBInfo *info, HostDBType record_type, int event, Event *e)
   {
     ip_text_buffer b;
     if (!output_json) {
       CHECK_SHOW(show("<table border=1>\n"));
-      CHECK_SHOW(show("<tr><td>%s</td><td>%s%s %s</td></tr>\n", "Type", r->round_robin ? "Round-Robin" : "",
-                      r->reverse_dns ? "Reverse DNS" : "", r->is_srv ? "SRV" : "DNS"));
+      CHECK_SHOW(show("<tr><td>%s</td><td>%s</td></tr>\n", "Type", name_of(record_type)));
 
-      if (r->perm_hostname()) {
-        CHECK_SHOW(show("<tr><td>%s</td><td>%s</td></tr>\n", "Hostname", r->perm_hostname()));
-      } else if (rr && r->is_srv && hostdb_rr) {
-        CHECK_SHOW(show("<tr><td>%s</td><td>%s</td></tr>\n", "Hostname", r->srvname(hostdb_rr)));
+      if (HostDBType::SRV == record_type) {
+        CHECK_SHOW(show("<tr><td>%s</td><td>%s</td></tr>\n", "Hostname", info->srvname()));
       }
 
       // Let's display the hash.
-      CHECK_SHOW(show("<tr><td>%s</td><td>%u</td></tr>\n", "App1", r->app.allotment.application1));
-      CHECK_SHOW(show("<tr><td>%s</td><td>%u</td></tr>\n", "App2", r->app.allotment.application2));
-      CHECK_SHOW(show("<tr><td>%s</td><td>%u</td></tr>\n", "LastFailure", r->app.http_data.last_failure));
-      if (!rr) {
-        CHECK_SHOW(show("<tr><td>%s</td><td>%s</td></tr>\n", "Stale", r->is_ip_stale() ? "Yes" : "No"));
-        CHECK_SHOW(show("<tr><td>%s</td><td>%s</td></tr>\n", "Timed-Out", r->is_ip_timeout() ? "Yes" : "No"));
-        CHECK_SHOW(show("<tr><td>%s</td><td>%d</td></tr>\n", "TTL", r->ip_time_remaining()));
-      }
+      CHECK_SHOW(show("<tr><td>%s</td><td>%u</td></tr>\n", "LastFailure", info->last_failure.load().time_since_epoch().count()));
 
-      if (rr && r->is_srv) {
-        CHECK_SHOW(show("<tr><td>%s</td><td>%d</td></tr>\n", "Weight", r->data.srv.srv_weight));
-        CHECK_SHOW(show("<tr><td>%s</td><td>%d</td></tr>\n", "Priority", r->data.srv.srv_priority));
-        CHECK_SHOW(show("<tr><td>%s</td><td>%d</td></tr>\n", "Port", r->data.srv.srv_port));
-        CHECK_SHOW(show("<tr><td>%s</td><td>%x</td></tr>\n", "Key", r->data.srv.key));
-      } else if (!r->is_srv) {
-        CHECK_SHOW(show("<tr><td>%s</td><td>%s</td></tr>\n", "IP", ats_ip_ntop(r->ip(), b, sizeof b)));
+      if (HostDBType::SRV == record_type) {
+        CHECK_SHOW(show("<tr><td>%s</td><td>%d</td></tr>\n", "Weight", info->data.srv.srv_weight));
+        CHECK_SHOW(show("<tr><td>%s</td><td>%d</td></tr>\n", "Priority", info->data.srv.srv_priority));
+        CHECK_SHOW(show("<tr><td>%s</td><td>%d</td></tr>\n", "Port", info->data.srv.srv_port));
+        CHECK_SHOW(show("<tr><td>%s</td><td>%x</td></tr>\n", "Key", info->data.srv.key));
+      } else {
+        CHECK_SHOW(show("<tr><td>%s</td><td>%s</td></tr>\n", "IP", info->data.ip.toString(b, sizeof b)));
       }
 
       CHECK_SHOW(show("</table>\n"));
     } else {
       CHECK_SHOW(show("{"));
-      CHECK_SHOW(show("\"%s\":\"%s%s%s\",", "type", (r->round_robin && !r->is_srv) ? "roundrobin" : "",
-                      r->reverse_dns ? "reversedns" : "", r->is_srv ? "srv" : "dns"));
+      CHECK_SHOW(show("\"%s\":\"%s\",", "type", name_of(record_type)));
 
-      if (r->perm_hostname()) {
-        CHECK_SHOW(show("\"%s\":\"%s\",", "hostname", r->perm_hostname()));
-      } else if (rr && r->is_srv && hostdb_rr) {
-        CHECK_SHOW(show("\"%s\":\"%s\",", "hostname", r->srvname(hostdb_rr)));
+      if (HostDBType::SRV == record_type) {
+        CHECK_SHOW(show("\"%s\":\"%s\",", "hostname", info->srvname()));
       }
 
-      CHECK_SHOW(show("\"%s\":\"%u\",", "app1", r->app.allotment.application1));
-      CHECK_SHOW(show("\"%s\":\"%u\",", "app2", r->app.allotment.application2));
-      CHECK_SHOW(show("\"%s\":\"%u\",", "lastfailure", r->app.http_data.last_failure));
-      if (!rr) {
-        CHECK_SHOW(show("\"%s\":\"%s\",", "stale", r->is_ip_stale() ? "yes" : "no"));
-        CHECK_SHOW(show("\"%s\":\"%s\",", "timedout", r->is_ip_timeout() ? "yes" : "no"));
-        CHECK_SHOW(show("\"%s\":\"%d\",", "ttl", r->ip_time_remaining()));
-      }
+      CHECK_SHOW(show("\"%s\":\"%u\",", "lastfailure", info->last_failure.load().time_since_epoch().count()));
 
-      if (rr && r->is_srv) {
-        CHECK_SHOW(show("\"%s\":\"%d\",", "weight", r->data.srv.srv_weight));
-        CHECK_SHOW(show("\"%s\":\"%d\",", "priority", r->data.srv.srv_priority));
-        CHECK_SHOW(show("\"%s\":\"%d\",", "port", r->data.srv.srv_port));
-        CHECK_SHOW(show("\"%s\":\"%x\",", "key", r->data.srv.key));
-      } else if (!r->is_srv) {
-        CHECK_SHOW(show("\"%s\":\"%s\"", "ip", ats_ip_ntop(r->ip(), b, sizeof b)));
+      if (HostDBType::SRV == record_type) {
+        CHECK_SHOW(show("\"%s\":\"%d\",", "weight", info->data.srv.srv_weight));
+        CHECK_SHOW(show("\"%s\":\"%d\",", "priority", info->data.srv.srv_priority));
+        CHECK_SHOW(show("\"%s\":\"%d\",", "port", info->data.srv.srv_port));
+        CHECK_SHOW(show("\"%s\":\"%x\",", "key", info->data.srv.key));
+      } else {
+        CHECK_SHOW(show("\"%s\":\"%s\"", "ip", info->data.ip.toString(b, sizeof b)));
       }
     }
     return EVENT_CONT;
@@ -1910,7 +1787,7 @@ struct ShowHostDB : public ShowCont {
   int
   showLookupDone(int event, Event *e)
   {
-    HostDBInfo *r = reinterpret_cast<HostDBInfo *>(e);
+    auto *r = reinterpret_cast<HostDBRecord *>(e);
 
     CHECK_SHOW(begin("HostDB Lookup"));
     if (name) {
@@ -1919,19 +1796,15 @@ struct ShowHostDB : public ShowCont {
       CHECK_SHOW(show("<H2>%u.%u.%u.%u</H2>\n", PRINT_IP(ip)));
     }
     if (r) {
-      showOne(r, false, event, e);
-      if (r->round_robin) {
-        HostDBRoundRobin *rr_data = r->rr();
-        if (rr_data) {
-          CHECK_SHOW(show("<table border=1>\n"));
-          CHECK_SHOW(show("<tr><td>%s</td><td>%d</td></tr>\n", "Total", rr_data->rrcount));
-          CHECK_SHOW(show("<tr><td>%s</td><td>%d</td></tr>\n", "Good", rr_data->good));
-          CHECK_SHOW(show("<tr><td>%s</td><td>%d</td></tr>\n", "Current", rr_data->current));
-          CHECK_SHOW(show("</table>\n"));
-
-          for (int i = 0; i < rr_data->rrcount; i++) {
-            showOne(&rr_data->info(i), true, event, e, rr_data);
-          }
+      auto rr_data = r->rr_info();
+      if (rr_data.count()) {
+        CHECK_SHOW(show("<table border=1>\n"));
+        CHECK_SHOW(show("<tr><td>%s</td><td>%d</td></tr>\n", "Total", r->rr_count));
+        CHECK_SHOW(show("<tr><td>%s</td><td>%d</td></tr>\n", "Current", r->_rr_idx.load()));
+        CHECK_SHOW(show("</table>\n"));
+
+        for (auto &item : rr_data) {
+          showOne(&item, r->record_type, event, e);
         }
       }
     } else {
@@ -2027,9 +1900,9 @@ struct HostDBTestReverse : public Continuation {
   mainEvent(int event, Event *e)
   {
     if (event == EVENT_HOST_DB_LOOKUP) {
-      HostDBInfo *i = reinterpret_cast<HostDBInfo *>(e);
+      auto *i = reinterpret_cast<HostDBRecord *>(e);
       if (i) {
-        rprintf(test, "HostDBTestReverse: reversed %s\n", i->hostname());
+        rprintf(test, "HostDBTestReverse: reversed %s\n", i->name());
       }
       outstanding--;
     }
@@ -2149,93 +2022,110 @@ HostDBFileContinuation::destroy()
 // We can't allow more than one update to be
 // proceeding at a time in any case so we might as well make these
 // globals.
-int HostDBFileUpdateActive = 0;
+std::atomic<bool> HostDBFileUpdateActive{false};
 
-static void
-ParseHostLine(Ptr<RefCountedHostsFileMap> &map, char *l)
+/* Container for temporarily holding data from the host file. For each FQDN there is a vector of IPv4
+ * and IPv6 addresses. These are used to generate the HostDBRecord instances that are stored persistently.
+ */
+using HostAddrMap = std::unordered_map<std::string_view, std::tuple<std::vector<IpAddr>, std::vector<IpAddr>>>;
+
+namespace
 {
-  Tokenizer elts(" \t");
-  int n_elts = elts.Initialize(l, SHARE_TOKS);
+constexpr unsigned IPV4_IDX = 0;
+constexpr unsigned IPV6_IDX = 1;
+} // namespace
 
+static void
+ParseHostLine(TextView line, HostAddrMap &map)
+{
   // Elements should be the address then a list of host names.
+  TextView addr_text = line.take_prefix_if(&isspace);
+  IpAddr addr;
+
   // Don't use RecHttpLoadIp because the address *must* be literal.
-  IpAddr ip;
-  if (n_elts > 1 && 0 == ip.load(elts[0])) {
-    for (int i = 1; i < n_elts; ++i) {
-      ts::ConstBuffer name(elts[i], strlen(elts[i]));
-      // If we don't have an entry already (host files only support single IPs for a given name)
-      if (map->hosts_file_map.find(name) == map->hosts_file_map.end()) {
-        map->hosts_file_map[name] = ip;
-      }
+  if (TS_SUCCESS != addr.load(addr_text)) {
+    return;
+  }
+
+  while (!line.ltrim_if(&isspace).empty()) {
+    TextView name = line.take_prefix_if(&isspace);
+    if (addr.isIp6()) {
+      std::get<IPV6_IDX>(map[name]).push_back(addr);
+    } else if (addr.isIp4()) {
+      std::get<IPV4_IDX>(map[name]).push_back(addr);
     }
   }
 }
 
 void
-ParseHostFile(const char *path, unsigned int hostdb_hostfile_check_interval_parse)
+ParseHostFile(ts::file::path const &path, ts_seconds hostdb_hostfile_check_interval_parse)
 {
-  Ptr<RefCountedHostsFileMap> parsed_hosts_file_ptr;
+  std::shared_ptr<HostFileMap> map;
 
   // Test and set for update in progress.
-  if (0 != ink_atomic_swap(&HostDBFileUpdateActive, 1)) {
+  bool flag = false;
+  if (!HostDBFileUpdateActive.compare_exchange_strong(flag, true)) {
     Debug("hostdb", "Skipped load of host file because update already in progress");
     return;
   }
-  Debug("hostdb", "Loading host file '%s'", path);
-
-  if (*path) {
-    ats_scoped_fd fd(open(path, O_RDONLY));
-    if (fd >= 0) {
-      struct stat info;
-      if (0 == fstat(fd, &info)) {
-        // +1 in case no terminating newline
-        int64_t size = info.st_size + 1;
-
-        parsed_hosts_file_ptr               = new RefCountedHostsFileMap;
-        parsed_hosts_file_ptr->HostFileText = static_cast<char *>(ats_malloc(size));
-        if (parsed_hosts_file_ptr->HostFileText) {
-          char *base = parsed_hosts_file_ptr->HostFileText;
-          char *limit;
-
-          size   = read(fd, parsed_hosts_file_ptr->HostFileText, info.st_size);
-          limit  = parsed_hosts_file_ptr->HostFileText + size;
-          *limit = 0;
-
-          // We need to get a list of all name/addr pairs so that we can
-          // group names for round robin records. Also note that the
-          // pairs have pointer back in to the text storage for the file
-          // so we need to keep that until we're done with @a pairs.
-          while (base < limit) {
-            char *spot = strchr(base, '\n');
-
-            // terminate the line.
-            if (nullptr == spot) {
-              spot = limit; // no trailing EOL, grab remaining
-            } else {
-              *spot = 0;
-            }
-
-            while (base < spot && isspace(*base)) {
-              ++base; // skip leading ws
-            }
-            if (*base != '#' && base < spot) { // non-empty non-comment line
-              ParseHostLine(parsed_hosts_file_ptr, base);
-            }
-            base = spot + 1;
-          }
-
-          hostdb_hostfile_update_timestamp = hostdb_current_interval;
+  Debug_bw("hostdb", R"(Loading host file "{}")", path);
+
+  if (!path.empty()) {
+    std::error_code ec;
+    std::string content = ts::file::load(path, ec);
+    if (!ec) {
+      HostAddrMap addr_map;
+      TextView text{content};
+      while (text) {
+        auto line = text.take_prefix_at('\n').ltrim_if(&isspace);
+        if (line.empty() || '#' == *line) {
+          continue;
         }
+        ParseHostLine(line, addr_map);
       }
+      // @a map should be loaded with all of the data, create the records.
+      map = std::make_shared<HostFileMap>();
+      // Common loading function for creating a record from the address vector.
+      auto loader = [](TextView key, std::vector<IpAddr> const &v) -> HostDBRecord::Handle {
+        HostDBRecord::Handle record{HostDBRecord::alloc(key, v.size())};
+        record->af_family = v.front().family(); // @a v is presumed family homogenous
+        auto rr_info      = record->rr_info();
+        auto spot         = v.begin();
+        for (auto &item : rr_info) {
+          item.assign(*spot++);
+        }
+        return record;
+      };
+      // Walk the temporary map and create the corresponding records for the persistent map.
+      for (auto const &[key, value] : addr_map) {
+        // Bit of subtlety to be able to search records with a view and not a string - the key
+        // must point at stable memory for the name, which is available in the record itself.
+        // Therefore the lookup for adding the record must be done using a view based in the record.
+        // It doesn't matter if it's the IPv4 or IPv6 record that's used, both are stable and equal
+        // to each other.
+        // IPv4
+        if (auto const &v = std::get<IPV4_IDX>(value); v.size() > 0) {
+          auto r                          = loader(key, v);
+          (*map)[r->name_view()].record_4 = r;
+        }
+        // IPv6
+        if (auto const &v = std::get<IPV6_IDX>(value); v.size() > 0) {
+          auto r                          = loader(key, v);
+          (*map)[r->name_view()].record_6 = r;
+        }
+      }
+
+      hostdb_hostfile_update_timestamp = hostdb_current_timestamp;
     }
   }
 
   // Swap the pointer
-  if (parsed_hosts_file_ptr != nullptr) {
-    hostDB.hosts_file_ptr = parsed_hosts_file_ptr;
+  if (map) {
+    std::unique_lock lock(hostDB.host_file_mutex);
+    hostDB.host_file = map;
   }
   // Mark this one as completed, so we can allow another update to happen
-  HostDBFileUpdateActive = 0;
+  HostDBFileUpdateActive = false;
 }
 
 //
@@ -2258,7 +2148,7 @@ struct HostDBRegressionContinuation : public Continuation {
   int i;
 
   int
-  mainEvent(int event, HostDBInfo *r)
+  mainEvent(int event, HostDBRecord *r)
   {
     (void)event;
 
@@ -2267,27 +2157,15 @@ struct HostDBRegressionContinuation : public Continuation {
     }
     if (event == EVENT_HOST_DB_LOOKUP) {
       --outstanding;
-      // since this is a lookup done, data is either hostdbInfo or nullptr
       if (r) {
-        rprintf(test, "hostdbinfo r=%x\n", r);
-        char const *hname = r->perm_hostname();
-        if (nullptr == hname) {
-          hname = "(null)";
-        }
-        rprintf(test, "hostdbinfo hostname=%s\n", hname);
-        rprintf(test, "hostdbinfo rr %x\n", r->rr());
+        rprintf(test, "HostDBRecord r=%x\n", r);
+        rprintf(test, "HostDBRecord hostname=%s\n", r->name());
         // If RR, print all of the enclosed records
-        if (r->rr()) {
-          rprintf(test, "hostdbinfo good=%d\n", r->rr()->good);
-          for (int x = 0; x < r->rr()->good; x++) {
-            ip_port_text_buffer ip_buf;
-            ats_ip_ntop(r->rr()->info(x).ip(), ip_buf, sizeof(ip_buf));
-            rprintf(test, "hostdbinfo RR%d ip=%s\n", x, ip_buf);
-          }
-        } else { // Otherwise, just the one will do
+        auto rr_info{r->rr_info()};
+        for (int x = 0; x < r->rr_count; ++x) {
           ip_port_text_buffer ip_buf;
-          ats_ip_ntop(r->ip(), ip_buf, sizeof(ip_buf));
-          rprintf(test, "hostdbinfo A ip=%s\n", ip_buf);
+          rr_info[x].data.ip.toString(ip_buf, sizeof(ip_buf));
+          rprintf(test, "hostdbinfo RR%d ip=%s\n", x, ip_buf);
         }
         ++success;
       } else {
@@ -2327,9 +2205,9 @@ struct HostDBRegressionContinuation : public Continuation {
 
 static const char *dns_test_hosts[] = {
   "www.apple.com", "www.ibm.com", "www.microsoft.com",
-  "www.coke.com", // RR record
-  "4.2.2.2",      // An IP-- since we don't expect resolution
-  "127.0.0.1",    // loopback since it has some special handling
+  "yahoo.com", // RR record
+  "4.2.2.2",   // An IP-- since we don't expect resolution
+  "127.0.0.1", // loopback since it has some special handling
 };
 
 REGRESSION_TEST(HostDBProcessor)(RegressionTest *t, int atype, int *pstatus)
@@ -2338,3 +2216,214 @@ REGRESSION_TEST(HostDBProcessor)(RegressionTest *t, int atype, int *pstatus)
 }
 
 #endif
+// -----
+void
+HostDBRecord::free()
+{
+  if (_iobuffer_index > 0) {
+    Debug("hostdb", "freeing %d bytes at [%p]", (1 << (7 + _iobuffer_index)), this);
+    ioBufAllocator[_iobuffer_index].free_void(static_cast<void *>(this));
+  }
+}
+
+HostDBRecord *
+HostDBRecord::alloc(TextView query_name, unsigned int rr_count, size_t srv_name_size)
+{
+  const ts::Scalar<8> qn_size = ts::round_up(query_name.size() + 1);
+  const ts::Scalar<8> r_size  = ts::round_up(sizeof(self_type) + qn_size + rr_count * sizeof(HostDBInfo) + srv_name_size);
+  int iobuffer_index          = iobuffer_size_to_index(r_size, hostdb_max_iobuf_index);
+  ink_release_assert(iobuffer_index >= 0);
+  auto ptr = ioBufAllocator[iobuffer_index].alloc_void();
+  memset(ptr, 0, r_size);
+  auto self = static_cast<self_type *>(ptr);
+  new (self) self_type();
+  self->_iobuffer_index = iobuffer_index;
+  self->_record_size    = r_size;
+
+  Debug("hostdb", "allocating %ld bytes for %.*s with %d RR records at [%p]", r_size.value(), int(query_name.size()),
+        query_name.data(), rr_count, self);
+
+  // where in our block of memory we are
+  int offset = sizeof(self_type);
+  memcpy(self->apply_offset<void>(offset), query_name);
+  offset += qn_size;
+  self->rr_offset = offset;
+  self->rr_count  = rr_count;
+  // Construct the info instances to a valid state.
+  for (auto &info : self->rr_info()) {
+    new (&info) std::remove_reference_t<decltype(info)>;
+  }
+
+  return self;
+}
+
+HostDBRecord::self_type *
+HostDBRecord::unmarshall(char *buff, unsigned size)
+{
+  if (size < sizeof(self_type)) {
+    return nullptr;
+  }
+  auto src = reinterpret_cast<self_type *>(buff);
+  ink_release_assert(size == src->_record_size);
+  auto ptr  = ioBufAllocator[src->_iobuffer_index].alloc_void();
+  auto self = static_cast<self_type *>(ptr);
+  new (self) self_type();
+  auto delta = sizeof(RefCountObj); // skip the VFTP and ref count.
+  memcpy(static_cast<std::byte *>(ptr) + delta, buff + delta, size - delta);
+  return self;
+}
+
+bool
+HostDBRecord::serve_stale_but_revalidate() const
+{
+  // the option is disabled
+  if (hostdb_serve_stale_but_revalidate <= 0) {
+    return false;
+  }
+
+  // ip_timeout_interval == DNS TTL
+  // hostdb_serve_stale_but_revalidate == number of seconds
+  // ip_age() is the number of seconds between now() and when the entry was inserted
+  if ((ip_timeout_interval + ts_seconds(hostdb_serve_stale_but_revalidate)) > ip_age()) {
+    Debug_bw("hostdb", "serving stale entry for {}, TTL: {}, serve_stale_for: {}, age: {} as requested by config", name(),
+             ip_timeout_interval, hostdb_serve_stale_but_revalidate, ip_age());
+    return true;
+  }
+
+  // otherwise, the entry is too old
+  return false;
+}
+
+HostDBInfo *
+HostDBRecord::select_best_srv(char *target, InkRand *rand, ts_time now, ts_seconds fail_window)
+{
+  ink_assert(rr_count <= 0 || static_cast<unsigned int>(rr_count) > hostdb_round_robin_max_count);
+
+  int i           = 0;
+  int live_n      = 0;
+  uint32_t weight = 0, p = INT32_MAX;
+  HostDBInfo *result = nullptr;
+  auto rr            = this->rr_info();
+  // Array of live targets, sized by @a live_n
+  HostDBInfo *live[rr.count()];
+  for (auto &target : rr) {
+    // skip dead upstreams.
+    if (rr[i].is_dead(now, fail_window)) {
+      continue;
+    }
+
+    if (target.data.srv.srv_priority <= p) {
+      p = target.data.srv.srv_priority;
+      weight += target.data.srv.srv_weight;
+      live[live_n++] = &target;
+    } else {
+      break;
+    }
+  };
+
+  if (live_n == 0 || weight == 0) { // no valid or weighted choice, use strict RR
+    result = this->select_next_rr(now, fail_window);
+  } else {
+    uint32_t xx = rand->random() % weight;
+    for (i = 0; i < live_n - 1 && xx >= live[i]->data.srv.srv_weight; ++i)
+      xx -= live[i]->data.srv.srv_weight;
+
+    result = live[i];
+  }
+
+  if (result) {
+    ink_strlcpy(target, this->name(), MAXDNAME);
+    return result;
+  }
+  return nullptr;
+}
+
+HostDBInfo *
+HostDBRecord::select_next_rr(ts_time now, ts_seconds fail_window)
+{
+  auto rr_info = this->rr_info();
+  for (unsigned idx = 0, limit = rr_info.count(); idx < limit; ++idx) {
+    auto &target = rr_info[this->next_rr()];
+    if (target.select(now, fail_window)) {
+      return &target;
+    }
+  }
+
+  return nullptr;
+}
+
+unsigned
+HostDBRecord::next_rr()
+{
+  auto raw_idx = ++_rr_idx;
+  // Modulus on an atomic is a bit tricky - need to make sure the value is always decremented by the
+  // modulus even if another thread incremented. Update to modulus value iff the value hasn't been
+  // incremented elsewhere. Eventually the "last" incrementer will do the update.
+  auto idx = raw_idx % rr_count;
+  _rr_idx.compare_exchange_weak(raw_idx, idx);
+  return idx;
+}
+
+HostDBInfo *
+HostDBRecord::find(sockaddr const *addr)
+{
+  for (auto &item : this->rr_info()) {
+    if (item.data.ip == addr) {
+      return &item;
+    }
+  }
+  return nullptr;
+}
+
+bool
+ResolveInfo::resolve_immediate()
+{
+  if (resolved_p) {
+    // nothing - already resolved.
+  } else if (IpAddr tmp; TS_SUCCESS == tmp.load(lookup_name)) {
+    ts::bwprint(ts::bw_dbg, "[resolve_immediate] success - FQDN '{}' is a valid IP address.", lookup_name);
+    Debug("hostdb", "%s", ts::bw_dbg.c_str());
+    addr.assign(tmp);
+    resolved_p = true;
+  }
+  return resolved_p;
+}
+
+bool
+ResolveInfo::set_active(HostDBInfo *info)
+{
+  active = info;
+  if (info) {
+    addr.assign(active->data.ip);
+    resolved_p = true;
+    return true;
+  }
+  resolved_p = false;
+  return false;
+}
+
+bool
+ResolveInfo::select_next_rr()
+{
+  if (active) {
+    if (auto rr_info{this->record->rr_info()}; rr_info.count() > 1) {
+      unsigned limit = active - rr_info.data(), idx = (limit + 1) % rr_info.count();
+      while ((idx = (idx + 1) % rr_info.count()) != limit && !rr_info[idx].is_alive())
+        ;
+      active = &rr_info[idx];
+      return idx != limit; // if the active record was actually changed.
+    }
+  }
+  return false;
+}
+
+bool
+ResolveInfo::set_upstream_address(IpAddr const &ip_addr)
+{
+  if (ip_addr.isValid()) {
+    addr.assign(ip_addr);
+    resolved_p = true;
+    return true;
+  }
+  return false;
+}
diff --git a/iocore/hostdb/I_HostDBProcessor.h b/iocore/hostdb/I_HostDBProcessor.h
index 0be0a81b4..6c34082c1 100644
--- a/iocore/hostdb/I_HostDBProcessor.h
+++ b/iocore/hostdb/I_HostDBProcessor.h
@@ -23,10 +23,14 @@
 
 #pragma once
 
+#include <chrono>
+#include <atomic>
+
 #include "tscore/HashFNV.h"
 #include "tscore/ink_time.h"
 #include "tscore/CryptoHash.h"
 #include "tscore/ink_align.h"
+#include "tscore/ink_inet.h"
 #include "tscore/ink_resolver.h"
 #include "tscore/HTTPVersion.h"
 #include "I_EventSystem.h"
@@ -46,6 +50,7 @@
 // Data
 //
 struct HostDBContinuation;
+struct ResolveInfo;
 
 //
 // The host database stores host information, most notably the
@@ -56,7 +61,17 @@ struct HostDBContinuation;
 // disk representation to decrease # of seeks.
 //
 extern int hostdb_enable;
-extern ink_time_t hostdb_current_interval;
+/** Epoch timestamp of the current hosts file check.
+ *
+ * This also functions as a cached version of ts_clock::now(). Since it is
+ * updated in backgroundEvent which runs every second, it should be
+ * approximately accurate to a second.
+ */
+extern ts_time hostdb_current_timestamp;
+
+/** How long before any DNS response is consided stale, regardless of DNS TTL.
+ * This corresponds to proxy.config.hostdb.verify_after.
+ */
 extern unsigned int hostdb_ip_stale_interval;
 extern unsigned int hostdb_ip_timeout_interval;
 extern unsigned int hostdb_ip_fail_timeout_interval;
@@ -84,339 +99,433 @@ makeHostHash(const char *string)
 // Types
 //
 
-/** Host information metadata used by various parts of HostDB.
- * It is stored as generic data in the cache.
- *
- * As a @c union only one of the members is valid, Which one depends on context data in the
- * @c HostDBInfo. This data is written literally to disk therefore if any change is made,
- * the @c object_version for the cache must be updated by modifying @c HostDBInfo::version.
- *
- * @see HostDBInfo::version
- */
-union HostDBApplicationInfo {
-  /// Generic storage. This is verified to be the size of the union.
-  struct application_data_allotment {
-    unsigned int application1;
-    unsigned int application2;
-  } allotment;
-
-  //////////////////////////////////////////////////////////
-  // http server attributes in the host database          //
-  //                                                      //
-  // http_version       - one of HTTPVersion              //
-  // last_failure       - UNIX time for the last time     //
-  //                      we tried the server & failed    //
-  // fail_count         - Number of times we tried and    //
-  //                       and failed to contact the host //
-  //////////////////////////////////////////////////////////
-  struct http_server_attr {
-    uint32_t last_failure;
-    HTTPVersion http_version;
-    uint8_t fail_count;
-    http_server_attr() : http_version() {}
-  } http_data;
-
-  struct application_data_rr {
-    unsigned int offset;
-  } rr;
-  HostDBApplicationInfo() : http_data() {}
-};
-
-struct HostDBRoundRobin;
+class HostDBRecord;
 
+/// Information for an SRV record.
 struct SRVInfo {
-  unsigned int srv_offset : 16;
+  unsigned int srv_offset : 16; ///< Memory offset from @c HostDBInfo to name.
   unsigned int srv_weight : 16;
   unsigned int srv_priority : 16;
   unsigned int srv_port : 16;
   unsigned int key;
 };
 
-struct HostDBInfo : public RefCountObj {
-  /** Internal IP address data.
-      This is at least large enough to hold an IPv6 address.
-  */
+/// Type of data stored.
+enum class HostDBType : uint8_t {
+  UNSPEC, ///< No valid data.
+  ADDR,   ///< IP address.
+  SRV,    ///< SRV record.
+  HOST    ///< Hostname (reverse DNS)
+};
+char const *name_of(HostDBType t);
 
-  static HostDBInfo *
-  alloc(int size = 0)
-  {
-    size += sizeof(HostDBInfo);
-    int iobuffer_index = iobuffer_size_to_index(size, hostdb_max_iobuf_index);
-    ink_release_assert(iobuffer_index >= 0);
-    void *ptr = ioBufAllocator[iobuffer_index].alloc_void();
-    memset(ptr, 0, size);
-    HostDBInfo *ret      = new (ptr) HostDBInfo();
-    ret->_iobuffer_index = iobuffer_index;
-    return ret;
-  }
+/** Information about a single target.
+ */
+struct HostDBInfo {
+  using self_type = HostDBInfo; ///< Self reference type.
 
-  void
-  free() override
-  {
-    ink_release_assert(from_alloc());
-    Debug("hostdb", "freeing %d bytes at [%p]", (1 << (7 + _iobuffer_index)), this);
-    ioBufAllocator[_iobuffer_index].free_void((void *)(this));
-  }
+  /// Default constructor.
+  HostDBInfo() = default;
 
-  /// Effectively the @c object_version for cache data.
-  /// This is used to indicate incompatible changes in the binary layout of HostDB records.
-  /// It must be updated if any such change is made, even if it is functionally equivalent.
-  static ts::VersionNumber
-  version()
-  {
-    /// - 1.0 Initial version.
-    /// - 1.1 tweak HostDBApplicationInfo::http_data.
-    return ts::VersionNumber(1, 1);
-  }
+  HostDBInfo &operator=(HostDBInfo const &that);
 
-  static HostDBInfo *
-  unmarshall(char *buf, unsigned int size)
-  {
-    if (size < sizeof(HostDBInfo)) {
-      return nullptr;
-    }
-    HostDBInfo *ret = HostDBInfo::alloc(size - sizeof(HostDBInfo));
-    int buf_index   = ret->_iobuffer_index;
-    memcpy((void *)ret, buf, size);
-    // Reset the refcount back to 0, this is a bit ugly-- but I'm not sure we want to expose a method
-    // to mess with the refcount, since this is a fairly unique use case
-    ret                  = new (ret) HostDBInfo();
-    ret->_iobuffer_index = buf_index;
-    return ret;
-  }
+  /// Absolute time of when this target failed.
+  /// A value of zero (@c TS_TIME_ZERO ) indicates no failure.
+  ts_time last_fail_time() const;
 
-  // return expiry time (in seconds since epoch)
-  ink_time_t
-  expiry_time() const
-  {
-    return ip_timestamp + ip_timeout_interval + hostdb_serve_stale_but_revalidate;
-  }
+  /// Target is alive - no known failure.
+  bool is_alive();
 
-  sockaddr *
-  ip()
-  {
-    return &data.ip.sa;
-  }
+  /// Target has failed and is still in the blocked time window.
+  bool is_dead(ts_time now, ts_seconds fail_window);
 
-  sockaddr const *
-  ip() const
-  {
-    return &data.ip.sa;
-  }
+  /** Select this target.
+   *
+   * @param now Current time.
+   * @param fail_window Failure window.
+   * @return Status of the selection.
+   *
+   * If a zombie is selected the failure time is updated to make it look dead to other threads in a thread safe
+   * manner. The caller should check @c last_fail_time to see if a zombie was selected.
+   */
+  bool select(ts_time now, ts_seconds fail_window);
 
-  char *hostname() const;
-  char *perm_hostname() const;
-  char *srvname(HostDBRoundRobin *rr) const;
+  /// Check if this info is valid.
+  bool is_valid() const;
 
-  /// Check if this entry is an element of a round robin entry.
-  /// If @c true then this entry is part of and was obtained from a round robin root. This is useful if the
-  /// address doesn't work - a retry can probably get a new address by doing another lookup and resolving to
-  /// a different element of the round robin.
-  bool
-  is_rr_elt() const
-  {
-    return 0 != round_robin_elt;
-  }
+  /// Mark this info as invalid.
+  void invalidate();
 
-  HostDBRoundRobin *rr();
+  /** Mark the entry as down.
+   *
+   * @param now Time of the failure.
+   * @return @c true if @a this was marked down, @c false if not.
+   *
+   * This can return @c false if the entry is already marked down, in which case the failure time is not updated.
+   */
+  bool mark_down(ts_time now);
 
-  unsigned int
-  ip_interval() const
-  {
-    return (hostdb_current_interval - ip_timestamp) & 0x7FFFFFFF;
-  }
+  /** Mark the target as up / alive.
+   *
+   * @return Previous alive state of the target.
+   */
+  bool mark_up();
 
-  int
-  ip_time_remaining() const
-  {
-    return static_cast<int>(ip_timeout_interval) - static_cast<int>(this->ip_interval());
-  }
+  char const *srvname() const;
 
-  bool
-  is_ip_stale() const
-  {
-    return ip_timeout_interval >= 2 * hostdb_ip_stale_interval && ip_interval() >= hostdb_ip_stale_interval;
-  }
+  /** Migrate data after a DNS update.
+   *
+   * @param that Source item.
+   *
+   * This moves only specific state information, it is not a generic copy.
+   */
+  void migrate_from(self_type const &that);
 
-  bool
-  is_ip_timeout() const
-  {
-    return ip_interval() >= ip_timeout_interval;
-  }
+  /// A target is either an IP address or an SRV record.
+  /// The type should be indicated by @c flags.f.is_srv;
+  union {
+    IpAddr ip;   ///< IP address / port data.
+    SRVInfo srv; ///< SRV record.
+  } data{IpAddr{}};
+
+  /// Data that migrates after updated DNS records are processed.
+  /// @see migrate_from
+  /// @{
+  /// Last time a failure was recorded.
+  std::atomic<ts_time> last_failure{TS_TIME_ZERO};
+  /// Count of connection failures
+  std::atomic<uint8_t> fail_count{0};
+  /// Expected HTTP version of the target based on earlier transactions.
+  HTTPVersion http_version = HTTP_INVALID;
+  /// @}
+
+  self_type &assign(IpAddr const &addr);
+
+protected:
+  self_type &assign(sa_family_t af, void const *addr);
+  self_type &assign(SRV const *srv, char const *name);
+
+  HostDBType type = HostDBType::UNSPEC; ///< Invalid data.
+
+  friend HostDBContinuation;
+};
 
-  bool
-  is_ip_fail_timeout() const
-  {
-    return ip_interval() >= hostdb_ip_fail_timeout_interval;
+inline HostDBInfo &
+HostDBInfo::operator=(HostDBInfo const &that)
+{
+  if (this != &that) {
+    memcpy(static_cast<void *>(this), static_cast<const void *>(&that), sizeof(*this));
   }
+  return *this;
+}
 
-  void
-  refresh_ip()
-  {
-    ip_timestamp = hostdb_current_interval;
-  }
+inline ts_time
+HostDBInfo::last_fail_time() const
+{
+  return last_failure;
+}
 
-  bool
-  serve_stale_but_revalidate() const
-  {
-    // the option is disabled
-    if (hostdb_serve_stale_but_revalidate <= 0) {
-      return false;
-    }
+inline bool
+HostDBInfo::is_alive()
+{
+  return this->last_fail_time() == TS_TIME_ZERO;
+}
 
-    // ip_timeout_interval == DNS TTL
-    // hostdb_serve_stale_but_revalidate == number of seconds
-    // ip_interval() is the number of seconds between now() and when the entry was inserted
-    if ((ip_timeout_interval + hostdb_serve_stale_but_revalidate) > ip_interval()) {
-      Debug("hostdb", "serving stale entry %d | %d | %d as requested by config", ip_timeout_interval,
-            hostdb_serve_stale_but_revalidate, ip_interval());
-      return true;
-    }
+inline bool
+HostDBInfo::is_dead(ts_time now, ts_seconds fail_window)
+{
+  auto last_fail = this->last_fail_time();
+  return (last_fail != TS_TIME_ZERO) && (last_fail + fail_window < now);
+}
+
+inline bool
+HostDBInfo::mark_up()
+{
+  auto t = last_failure.exchange(TS_TIME_ZERO);
+  return t != TS_TIME_ZERO;
+}
+
+inline bool
+HostDBInfo::mark_down(ts_time now)
+{
+  auto t0{TS_TIME_ZERO};
+  return last_failure.compare_exchange_strong(t0, now);
+}
 
-    // otherwise, the entry is too old
-    return false;
+inline bool
+HostDBInfo::select(ts_time now, ts_seconds fail_window)
+{
+  auto t0 = this->last_fail_time();
+  if (t0 == TS_TIME_ZERO) {
+    return true; // it's alive and so is valid for selection.
   }
+  // Success means this is a zombie and this thread updated the failure time.
+  return (t0 + fail_window < now) && last_failure.compare_exchange_strong(t0, now);
+}
+
+inline void
+HostDBInfo::migrate_from(HostDBInfo::self_type const &that)
+{
+  this->last_failure = that.last_failure.load();
+  this->http_version = that.http_version;
+}
+
+inline bool
+HostDBInfo::is_valid() const
+{
+  return type != HostDBType::UNSPEC;
+}
+
+inline void
+HostDBInfo::invalidate()
+{
+  type = HostDBType::UNSPEC;
+}
 
-  /*
-   * Given the current time `now` and the fail_window, determine if this real is alive
+// ----
+/** Root item for HostDB.
+ * This is the container for HostDB data. It is always an array of @c HostDBInfo instances plus metadata.
+ * All strings are C-strings and therefore don't need a distinct size.
+ *
+ */
+class HostDBRecord : public RefCountObj
+{
+  friend struct HostDBContinuation;
+  friend struct ShowHostDB;
+  using self_type = HostDBRecord;
+
+  /// Size of the IO buffer block owned by @a this.
+  /// If negative @a this is in not allocated memory.
+  int _iobuffer_index{-1};
+  /// Actual size of the data.
+  unsigned _record_size = sizeof(self_type);
+
+public:
+  HostDBRecord()                      = default;
+  HostDBRecord(self_type const &that) = delete;
+
+  using Handle = Ptr<HostDBRecord>; ///< Shared pointer type to hold an instance.
+
+  /** Allocate an instance from the IOBuffers.
+   *
+   * @param query_name Name of the query for the record.
+   * @param rr_count Number of info instances.
+   * @param srv_name_size Storage for SRV names, if any.
+   * @return An instance sufficient to hold the specified data.
+   *
+   * The query name will stored and initialized, and the info instances initialized.
    */
-  bool
-  is_alive(ink_time_t now, int32_t fail_window)
-  {
-    unsigned int last_failure = app.http_data.last_failure;
-
-    if (last_failure == 0 || (unsigned int)(now - fail_window) > last_failure) {
-      return true;
-    } else {
-      // Entry is marked down.  Make sure some nasty clock skew
-      //  did not occur.  Use the retry time to set an upper bound
-      //  as to how far in the future we should tolerate bogus last
-      //  failure times.  This sets the upper bound that we would ever
-      //  consider a server down to 2*down_server_timeout
-      if ((unsigned int)(now + fail_window) < last_failure) {
-        app.http_data.last_failure = 0;
-        return false;
-      }
-      return false;
-    }
-  }
+  static self_type *alloc(ts::TextView query_name, unsigned rr_count, size_t srv_name_size = 0);
 
-  bool
-  is_failed() const
-  {
-    return !((is_srv && data.srv.srv_offset) || (reverse_dns && data.hostname_offset) || ats_is_ip(ip()));
-  }
+  /// Type of data stored in this record.
+  HostDBType record_type = HostDBType::UNSPEC;
 
-  void
-  set_failed()
-  {
-    if (is_srv) {
-      data.srv.srv_offset = 0;
-    } else if (reverse_dns) {
-      data.hostname_offset = 0;
-    } else {
-      ats_ip_invalidate(ip());
-    }
-  }
+  /// IP family of this record.
+  sa_family_t af_family = AF_UNSPEC;
+
+  /// Offset from @a this to the VLA.
+  unsigned short rr_offset = 0;
 
+  /// Number of @c HostDBInfo instances.
+  unsigned short rr_count = 0;
+
+  /// Timing data for switch records in the RR.
+  std::atomic<ts_time> rr_ctime{TS_TIME_ZERO};
+
+  /// Hash key.
   uint64_t key{0};
 
-  // Application specific data. NOTE: We need an integral number of
-  // these per block. This structure is 32 bytes. (at 200k hosts =
-  // 8 Meg). Which gives us 7 bytes of application information.
-  HostDBApplicationInfo app;
+  /// When the DNS response was received.
+  ts_time ip_timestamp;
 
-  union {
-    IpEndpoint ip;                ///< IP address / port data.
-    unsigned int hostname_offset; ///< Some hostname thing.
-    SRVInfo srv;
-  } data;
+  /// Valid duration of the DNS response data.
+  /// In the code this functions as the TTL in HostDB calcuations, but may not
+  /// be the response's TTL based upon configuration such as
+  /// proxy.config.hostdb.ttl_mode.
+  ts_seconds ip_timeout_interval;
 
-  unsigned int hostname_offset{0}; // always maintain a permanent copy of the hostname for non-rev dns records.
+  /** Atomically advance the round robin index.
+   *
+   * If multiple threads call this simultaneously each thread will get a distinct return value.
+   *
+   * @return The new round robin index.
+   */
+  unsigned next_rr();
+
+  /** Pick the next round robin and update the record atomically.
+   *
+   * @note This may select a zombie server and reserve it for the caller, therefore the caller must
+   * attempt to connect to the selected target if possible.
+   *
+   * @param now Current time to use for aliveness calculations.
+   * @param fail_window Blackout time for dead servers.
+   * @return Status of the updated target.
+   *
+   * If the return value is @c HostDBInfo::Status::DEAD this means all targets are dead and there is
+   * no valid upstream.
+   *
+   * @note Concurrency - this is not done under lock and depends on the caller for correct use.
+   * For strict round robin, it is a feature that every call will get a distinct index. For
+   * timed round robin, the caller must arrange to have only one thread call this per time interval.
+   */
+  HostDBInfo *select_next_rr(ts_time now, ts_seconds fail_window);
 
-  unsigned int ip_timestamp{0};
+  /// Check if this record is of SRV targets.
+  bool is_srv() const;
 
-  unsigned int ip_timeout_interval{0}; // bounded between 1 and HOST_DB_MAX_TTL (0x1FFFFF, 24 days)
+  /** Query name for the record.
+   * @return A C-string.
+   * If this is a @c HOST record, this is the resolved named and the query was based on the IP address.
+   * Otherwise this is the name used in the DNS query.
+   */
+  char const *name() const;
+
+  /** Query name for the record.
+   * @return A view.
+   * If this is a @c HOST record, this is the resolved named and the query was based on the IP address.
+   * Otherwise this is the name used in the DNS query.
+   * @note Although not included in the view, the name is always nul terminated and the string can
+   * be used as a C-string.
+   */
+  ts::TextView name_view() const;
 
-  unsigned int is_srv : 1;
-  unsigned int reverse_dns : 1;
+  /// Get the array of info instances.
+  ts::MemSpan<HostDBInfo> rr_info();
 
-  unsigned int round_robin : 1;     // This is the root of a round robin block
-  unsigned int round_robin_elt : 1; // This is an address in a round robin block
+  /** Find a host record by IP address.
+   *
+   * @param addr Address key.
+   * @return A pointer to the info instance if a match is found, @c nullptr if not.
+   */
+  HostDBInfo *find(sockaddr const *addr);
+
+  /** Select an upstream target.
+   *
+   * @param now Current time.
+   * @param fail_window Dead server blackout time.
+   * @param hash_addr Inbound remote IP address.
+   * @return A selected target, or @c nullptr if there are no valid targets.
+   *
+   * This accounts for the round robin setting. The default is to use "client affinity" in
+   * which case @a hash_addr is as a hash seed to select the target.
+   *
+   * This may select a zombie target, which can be detected by checking the target's last
+   * failure time. If it is not @c TS_TIME_ZERO the target is a zombie. Other transactions will
+   * be blocked from selecting that target until @a fail_window time has passed.
+   *
+   * In cases other than strict round robin, a base target is selected. If valid, that is returned,
+   * but if not then the targets in this record are searched until a valid one is found. The result
+   * is this can be called to select a target for failover when a previous target fails.
+   */
+  HostDBInfo *select_best_http(ts_time now, ts_seconds fail_window, sockaddr const *hash_addr);
+  HostDBInfo *select_best_srv(char *target, InkRand *rand, ts_time now, ts_seconds fail_window);
 
-  HostDBInfo() : _iobuffer_index{-1} {}
+  bool is_failed() const;
 
-  HostDBInfo(HostDBInfo const &src) : RefCountObj()
-  {
-    memcpy(static_cast<void *>(this), static_cast<const void *>(&src), sizeof(*this));
-    _iobuffer_index = -1;
-  }
+  void set_failed();
 
-  HostDBInfo &
-  operator=(HostDBInfo const &src)
-  {
-    if (this != &src) {
-      int iob_idx = _iobuffer_index;
-      memcpy(static_cast<void *>(this), static_cast<const void *>(&src), sizeof(*this));
-      _iobuffer_index = iob_idx;
-    }
-    return *this;
-  }
+  /// @return The time point when the item expires.
+  ts_time expiry_time() const;
 
-  bool
-  from_alloc() const
-  {
-    return _iobuffer_index >= 0;
-  }
+  /// @return The age of the DNS response.
+  ts_seconds ip_age() const;
 
-private:
-  // The value of this will be -1 for objects that are not created by the alloc() static member function.
-  int _iobuffer_index;
-};
+  /// @return How long before the DNS response becomes stale (i.e., exceeds @a
+  /// ip_timeout_interval from the time of the response).
+  ts_seconds ip_time_remaining() const;
+
+  /// @return Whether the age of the DNS response exceeds @a the user's
+  /// configured proxy.config.hostdb.verify_after value.
+  bool is_ip_configured_stale() const;
+
+  /** Whether we have exceeded the DNS response's TTL (i.e., whether the DNS
+   * response is stale). */
+  bool is_ip_timeout() const;
+
+  bool is_ip_fail_timeout() const;
+
+  void refresh_ip();
 
-struct HostDBRoundRobin {
-  /** Total number (to compute space used). */
-  short rrcount = 0;
+  /** Whether the DNS response can still be used per
+   * proxy.config.hostdb.serve_stale_for configuration.
+   *
+   * @return False if serve_stale_for is not configured or if the DNS
+   * response's age is less than TTL + the serve_stale_for value. Note that
+   * this function will return true for DNS responses whose age is less than
+   * TTL (i.e., for responses that are not yet stale).
+   */
+  bool serve_stale_but_revalidate() const;
+
+  /// Deallocate @a this.
+  void free() override;
 
-  /** Number which have not failed a connect. */
-  short good = 0;
+  /** The current round robin index.
+   *
+   * @return The current index.
+   *
+   * @note The internal index may be out of range due to concurrency constraints - this insures the
+   * returned valu is in range.
+   */
+  unsigned short rr_idx() const;
 
-  unsigned short current    = 0;
-  ink_time_t timed_rr_ctime = 0;
+  /** Offset from the current round robin index.
+   *
+   * @param delta Distance from the current index.
+   * @return The effective index.
+   */
+  unsigned short rr_idx(unsigned short delta) const;
 
-  // This is the equivalent of a variable length array, we can't use a VLA because
-  // HostDBInfo is a non-POD type-- so this is the best we can do.
-  HostDBInfo &
-  info(short n)
+  /// The index of @a target in this record.
+  int index_of(HostDBInfo const *target) const;
+
+  /** Allocation and initialize an instance from a serialized buffer.
+   *
+   * @param buff Serialization data.
+   * @param size Size of @a buff.
+   * @return An instance initialized from @a buff.
+   */
+  static self_type *unmarshall(char *buff, unsigned size);
+
+  /// Database version.
+  static constexpr ts::VersionNumber Version{3, 0};
+
+protected:
+  /// Current active info.
+  /// @note This value may be out of range due to the difficulty of synchronization, therefore
+  /// must always be taken modulus @c rr_count when used. Use the @c rr_idx() method unless
+  /// raw access is required.
+  std::atomic<unsigned short> _rr_idx = 0;
+
+  /** Access an internal object at @a offset.
+   *
+   * @tparam T Type of object.
+   * @param offset Offset of object.
+   * @return A pointer to the object of type @a T.
+   *
+   * @a offset is applied to @a this record and the result cast to a pointer to @a T.
+   *
+   * @note @a offset based at @a this.
+   */
+  template <typename T>
+  T *
+  apply_offset(unsigned offset)
   {
-    ink_assert(n < rrcount && n >= 0);
-    return *((HostDBInfo *)((char *)this + sizeof(HostDBRoundRobin)) + n);
+    return reinterpret_cast<T *>(reinterpret_cast<char *>(this) + offset);
   }
 
-  // Return the allocation size of a HostDBRoundRobin struct suitable for storing
-  // "count" HostDBInfo records.
-  static unsigned
-  size(unsigned count, unsigned srv_len = 0)
+  template <typename T>
+  T const *
+  apply_offset(unsigned offset) const
   {
-    ink_assert(count > 0);
-    return INK_ALIGN((sizeof(HostDBRoundRobin) + (count * sizeof(HostDBInfo)) + srv_len), 8);
+    return reinterpret_cast<T const *>(reinterpret_cast<char const *>(this) + offset);
   }
 
-  /** Find the index of @a addr in member @a info.
-      @return The index if found, -1 if not found.
-  */
-  int index_of(sockaddr const *addr);
-  HostDBInfo *find_ip(sockaddr const *addr);
-  // Find the srv target
-  HostDBInfo *find_target(const char *target);
-  /** Select the next entry after @a addr.
-      @note If @a addr isn't an address in the round robin nothing is updated.
-      @return The selected entry or @c nullptr if @a addr wasn't present.
-   */
-  HostDBInfo *select_next(sockaddr const *addr);
-  HostDBInfo *select_best_http(sockaddr const *client_ip, ink_time_t now, int32_t fail_window);
-  HostDBInfo *select_best_srv(char *target, InkRand *rand, ink_time_t now, int32_t fail_window);
-  HostDBRoundRobin() {}
+  union {
+    uint16_t all;
+    struct {
+      unsigned failed_p : 1; ///< DNS error.
+    } f;
+  } flags{0};
 };
 
 struct HostDBCache;
@@ -424,10 +533,123 @@ struct HostDBHash;
 
 // Prototype for inline completion function or
 //  getbyname_imm()
-typedef void (Continuation::*cb_process_result_pfn)(HostDBInfo *r);
+using cb_process_result_pfn = void (Continuation::*)(HostDBRecord *r);
 
 Action *iterate(Continuation *cont);
 
+/** Information for doing host resolution for a request.
+ *
+ * This is effectively a state object for a request attempting to connect upstream. Information about its attempt
+ * that are local to the request are kept here, while shared data is accessed via the @c HostDBInfo pointers.
+ *
+ * A primitive version of the IP address generator concept.
+ */
+struct ResolveInfo {
+  using self_type = ResolveInfo; ///< Self reference type.
+
+  /// Not quite sure what this is for.
+  enum UpstreamResolveStyle { UNDEFINED_LOOKUP, ORIGIN_SERVER, PARENT_PROXY, HOST_NONE };
+
+  /** Origin server address source selection.
+
+      If config says to use CTA (client target addr) state is TRY_CLIENT, otherwise it
+      remains the default. If the connect fails then we switch to a USE. We go to USE_HOSTDB if (1)
+      the HostDB lookup is successful and (2) some address other than the CTA is available to try.
+      Otherwise we keep retrying on the CTA (USE_CLIENT) up to the max retry value.  In essence we
+      try to treat the CTA as if it were another RR value in the HostDB record.
+   */
+  enum class OS_Addr {
+    TRY_DEFAULT, ///< Initial state, use what config says.
+    TRY_HOSTDB,  ///< Try HostDB data.
+    TRY_CLIENT,  ///< Try client target addr.
+    USE_HOSTDB,  ///< Force use of HostDB target address.
+    USE_CLIENT,  ///< Force client target addr.
+    USE_API      ///< Use the API provided address.
+  };
+
+  ResolveInfo()  = default;
+  ~ResolveInfo() = default;
+
+  /// Keep a reference to the base HostDB object, so it doesn't get GC'd.
+  Ptr<HostDBRecord> record;
+  HostDBInfo *active = nullptr; ///< Active host record.
+
+  /// Working address. The meaning / source of the value depends on other elements.
+  /// This is the "resolved" address if @a resolved_p is @c true.
+  IpEndpoint addr;
+
+  int attempts = 0; ///< Number of connection attempts.
+
+  char const *lookup_name             = nullptr;
+  char srv_hostname[MAXDNAME]         = {0};
+  const sockaddr *inbound_remote_addr = nullptr; ///< Remote address of inbound client - used for hashing.
+  in_port_t srv_port                  = 0;       ///< Port from SRV lookup or API call.
+
+  OS_Addr os_addr_style           = OS_Addr::TRY_DEFAULT;
+  HostResStyle host_res_style     = HOST_RES_IPV4;
+  UpstreamResolveStyle looking_up = UNDEFINED_LOOKUP;
+
+  HTTPVersion http_version = HTTP_INVALID;
+
+  bool resolved_p = false; ///< If there is a valid, resolved address in @a addr.
+
+  /// Flag for @a addr being set externally.
+  //  bool api_addr_set_p = false;
+
+  /*** Set to true by default.  If use_client_target_address is set
+   * to 1, this value will be set to false if the client address is
+   * not in the DNS pool */
+  bool cta_validated_p = true;
+
+  bool set_active(HostDBInfo *info);
+
+  bool set_active(sockaddr const *s);
+
+  bool set_active(std::nullptr_t);
+
+  /** Force a resolved address.
+   *
+   * @param sa Address to use for the upstream.
+   * @return @c true if successful, @c false if error.
+   *
+   * This fails if @a sa isn't a valid IP address.
+   */
+  bool set_upstream_address(const sockaddr *sa);
+
+  bool set_upstream_address(IpAddr const &ip_addr);
+
+  void set_upstream_port(in_port_t port);
+
+  /** Check and (if possible) immediately resolve the upstream address without consulting the HostDB.
+   * The cases where this is successful are
+   * - The address is already resolved (@a resolved_p is @c true).
+   * - The upstream was set explicitly.
+   * - The hostname is a valid IP address.
+   *
+   * @return @c true if the upstream address was resolved, @c false if not.
+   */
+  bool resolve_immediate();
+
+  /** Mark the active target as dead.
+   *
+   * @param now Time of failure.
+   * @return @c true if the server was marked as dead, @c false if not.
+   *
+   */
+  bool mark_active_server_dead(ts_time now);
+
+  /** Mark the active target as alive.
+   *
+   * @return @c true if the target changed state.
+   */
+  bool mark_active_server_alive();
+
+  /// Select / resolve to the next RR entry for the record.
+  bool select_next_rr();
+
+  bool is_srv() const;
+};
+
 /** The Host Database access interface. */
 struct HostDBProcessor : public Processor {
   friend struct HostDBSync;
@@ -486,29 +708,6 @@ struct HostDBProcessor : public Processor {
   /** Lookup Hostinfo by addr */
   Action *getbyaddr_re(Continuation *cont, sockaddr const *aip);
 
-  /** Set the application information (fire-and-forget). */
-  void
-  setbyname_appinfo(char *hostname, int len, int port, HostDBApplicationInfo *app)
-  {
-    sockaddr_in addr;
-    ats_ip4_set(&addr, INADDR_ANY, port);
-    setby(hostname, len, ats_ip_sa_cast(&addr), app);
-  }
-
-  void
-  setbyaddr_appinfo(sockaddr const *addr, HostDBApplicationInfo *app)
-  {
-    this->setby(nullptr, 0, addr, app);
-  }
-
-  void
-  setbyaddr_appinfo(in_addr_t ip, HostDBApplicationInfo *app)
-  {
-    sockaddr_in addr;
-    ats_ip4_set(&addr, ip);
-    this->setby(nullptr, 0, ats_ip_sa_cast(&addr), app);
-  }
-
   /** Configuration. */
   static int hostdb_strict_round_robin;
   static int hostdb_timed_round_robin;
@@ -524,21 +723,154 @@ struct HostDBProcessor : public Processor {
 
 private:
   Action *getby(Continuation *cont, cb_process_result_pfn cb_process_result, HostDBHash &hash, Options const &opt);
+};
 
-public:
-  /** Set something.
-      @a aip can carry address and / or port information. If setting just
-      by a port value, the address should be set to INADDR_ANY which is of
-      type IPv4.
-   */
-  void setby(const char *hostname,      ///< Hostname.
-             int len,                   ///< Length of hostname.
-             sockaddr const *aip,       ///< Address and/or port.
-             HostDBApplicationInfo *app ///< I don't know.
-  );
+inline bool
+HostDBRecord::is_srv() const
+{
+  return HostDBType::SRV == record_type;
+}
 
-  void setby_srv(const char *hostname, int len, const char *target, HostDBApplicationInfo *app);
-};
+inline char const *
+HostDBRecord::name() const
+{
+  return this->apply_offset<char const>(sizeof(self_type));
+}
+
+inline ts::TextView
+HostDBRecord::name_view() const
+{
+  return {this->name(), ts::TextView::npos};
+}
+
+inline ts_time
+HostDBRecord::expiry_time() const
+{
+  return ip_timestamp + ip_timeout_interval + ts_seconds(hostdb_serve_stale_but_revalidate);
+}
+
+inline ts_seconds
+HostDBRecord::ip_age() const
+{
+  static constexpr ts_seconds ZERO{0};
+  static constexpr ts_seconds MAX{0x7FFFFFFF};
+  return std::clamp(std::chrono::duration_cast<ts_seconds>(hostdb_current_timestamp - ip_timestamp), ZERO, MAX);
+}
+
+inline ts_seconds
+HostDBRecord::ip_time_remaining() const
+{
+  static constexpr ts_seconds ZERO{0};
+  static constexpr ts_seconds MAX{0x7FFFFFFF};
+  return std::clamp(std::chrono::duration_cast<ts_seconds>(ip_timeout_interval - this->ip_age()), ZERO, MAX);
+}
+
+inline bool
+HostDBRecord::is_ip_configured_stale() const
+{
+  return (
+    ((ip_timeout_interval >= ts_seconds(2 * hostdb_ip_stale_interval)) && (ip_age() >= ts_seconds(hostdb_ip_stale_interval))));
+}
+
+inline bool
+HostDBRecord::is_ip_timeout() const
+{
+  return ip_age() >= ip_timeout_interval;
+}
+
+inline bool
+HostDBRecord::is_ip_fail_timeout() const
+{
+  return ip_age() >= ts_seconds(hostdb_ip_fail_timeout_interval);
+}
+
+inline void
+HostDBRecord::refresh_ip()
+{
+  ip_timestamp = hostdb_current_timestamp;
+}
+
+inline ts::MemSpan<HostDBInfo>
+HostDBRecord::rr_info()
+{
+  return {this->apply_offset<HostDBInfo>(rr_offset), rr_count};
+}
+
+inline bool
+HostDBRecord::is_failed() const
+{
+  return flags.f.failed_p;
+}
+
+inline void
+HostDBRecord::set_failed()
+{
+  flags.f.failed_p = true;
+}
+
+inline unsigned short
+HostDBRecord::rr_idx() const
+{
+  return _rr_idx % rr_count;
+}
+
+inline unsigned short
+HostDBRecord::rr_idx(unsigned short delta) const
+{
+  return (_rr_idx + delta) % rr_count;
+}
+
+inline int
+HostDBRecord::index_of(HostDBInfo const *target) const
+{
+  return target ? target - this->apply_offset<HostDBInfo>(rr_offset) : -1;
+}
+
+// --
+
+inline bool
+ResolveInfo::set_active(sockaddr const *s)
+{
+  return this->set_active(record->find(s));
+}
+
+inline bool
+ResolveInfo::mark_active_server_alive()
+{
+  return active->mark_up();
+}
+
+inline bool
+ResolveInfo::mark_active_server_dead(ts_time now)
+{
+  return active != nullptr && active->mark_down(now);
+}
+
+inline bool ResolveInfo::set_active(std::nullptr_t)
+{
+  active     = nullptr;
+  resolved_p = false;
+  return false;
+}
+
+inline bool
+ResolveInfo::set_upstream_address(sockaddr const *sa)
+{
+  return resolved_p = addr.assign(sa).isValid();
+}
+
+inline void
+ResolveInfo::set_upstream_port(in_port_t port)
+{
+  srv_port = port;
+}
+
+inline bool
+ResolveInfo::is_srv() const
+{
+  return record && record->is_srv();
+}
+// ---
 
 void run_HostDBTest();
 
diff --git a/iocore/hostdb/P_HostDBProcessor.h b/iocore/hostdb/P_HostDBProcessor.h
index 7a05013c7..3ebd270a8 100644
--- a/iocore/hostdb/P_HostDBProcessor.h
+++ b/iocore/hostdb/P_HostDBProcessor.h
@@ -27,6 +27,8 @@
 
 #pragma once
 
+#include <shared_mutex>
+
 #include "I_HostDBProcessor.h"
 #include "tscore/TsBuffer.h"
 
@@ -51,7 +53,7 @@ extern int hostdb_ttl_mode;
 extern int hostdb_srv_enabled;
 
 // extern int hostdb_timestamp;
-extern int hostdb_sync_frequency;
+extern ts_seconds hostdb_sync_frequency;
 extern int hostdb_disable_reverse_lookup;
 
 // Static configuration information
@@ -74,26 +76,6 @@ enum HostDBMark {
  */
 extern const char *string_for(HostDBMark mark);
 
-inline unsigned int
-HOSTDB_CLIENT_IP_HASH(sockaddr const *lhs, sockaddr const *rhs)
-{
-  unsigned int zret = ~static_cast<unsigned int>(0);
-  if (ats_ip_are_compatible(lhs, rhs)) {
-    if (ats_is_ip4(lhs)) {
-      in_addr_t ip1 = ats_ip4_addr_cast(lhs);
-      in_addr_t ip2 = ats_ip4_addr_cast(rhs);
-      zret          = (ip1 >> 16) ^ ip1 ^ ip2 ^ (ip2 >> 16);
-    } else if (ats_is_ip6(lhs)) {
-      uint32_t const *ip1 = ats_ip_addr32_cast(lhs);
-      uint32_t const *ip2 = ats_ip_addr32_cast(rhs);
-      for (int i = 0; i < 4; ++i, ++ip1, ++ip2) {
-        zret ^= (*ip1 >> 16) ^ *ip1 ^ *ip2 ^ (*ip2 >> 16);
-      }
-    }
-  }
-  return zret & 0xFFFF;
-}
-
 //
 // Constants
 //
@@ -170,21 +152,12 @@ extern RecRawStatBlock *hostdb_rsb;
 
 #define HOSTDB_DECREMENT_THREAD_DYN_STAT(_s, _t) RecIncrRawStatSum(hostdb_rsb, _t, (int)_s, -1);
 
-struct CmpConstBuffferCaseInsensitive {
-  bool
-  operator()(ts::ConstBuffer a, ts::ConstBuffer b) const
-  {
-    return ptr_len_casecmp(a._ptr, a._size, b._ptr, b._size) < 0;
-  }
+struct HostFileRecord {
+  HostDBRecord::Handle record_4;
+  HostDBRecord::Handle record_6;
 };
 
-// Our own typedef for the host file mapping
-typedef std::map<ts::ConstBuffer, IpAddr, CmpConstBuffferCaseInsensitive> HostsFileMap;
-// A to hold a ref-counted map
-struct RefCountedHostsFileMap : public RefCountObj {
-  HostsFileMap hosts_file_map;
-  ats_scoped_str HostFileText;
-};
+using HostFileMap = std::unordered_map<ts::TextView, HostFileRecord, std::hash<std::string_view>>;
 
 //
 // HostDBCache (Private)
@@ -192,9 +165,11 @@ struct RefCountedHostsFileMap : public RefCountObj {
 struct HostDBCache {
   int start(int flags = 0);
   // Map to contain all of the host file overrides, initialize it to empty
-  Ptr<RefCountedHostsFileMap> hosts_file_ptr;
+  std::shared_ptr<HostFileMap> host_file;
+  std::shared_mutex host_file_mutex;
+
   // TODO: make ATS call a close() method or something on shutdown (it does nothing of the sort today)
-  RefCountCache<HostDBInfo> *refcountcache = nullptr;
+  RefCountCache<HostDBRecord> *refcountcache = nullptr;
 
   // TODO configurable number of items in the cache
   Queue<HostDBContinuation, Continuation::Link_link> *pending_dns = nullptr;
@@ -202,189 +177,9 @@ struct HostDBCache {
   Queue<HostDBContinuation, Continuation::Link_link> *remoteHostDBQueue = nullptr;
   HostDBCache();
   bool is_pending_dns_for_hash(const CryptoHash &hash);
-};
-
-inline int
-HostDBRoundRobin::index_of(sockaddr const *ip)
-{
-  bool bad = (rrcount <= 0 || (unsigned int)rrcount > hostdb_round_robin_max_count || good <= 0 ||
-              (unsigned int)good > hostdb_round_robin_max_count);
-  if (bad) {
-    ink_assert(!"bad round robin size");
-    return -1;
-  }
-
-  for (int i = 0; i < good; i++) {
-    if (ats_ip_addr_eq(ip, info(i).ip())) {
-      return i;
-    }
-  }
-
-  return -1;
-}
-
-inline HostDBInfo *
-HostDBRoundRobin::find_ip(sockaddr const *ip)
-{
-  int idx = this->index_of(ip);
-  return idx < 0 ? nullptr : &info(idx);
-}
-
-inline HostDBInfo *
-HostDBRoundRobin::select_next(sockaddr const *ip)
-{
-  HostDBInfo *zret = nullptr;
-  if (good > 1) {
-    int idx = this->index_of(ip);
-    if (idx >= 0) {
-      idx  = (idx + 1) % good;
-      zret = &info(idx);
-    }
-  }
-  return zret;
-}
-
-inline HostDBInfo *
-HostDBRoundRobin::find_target(const char *target)
-{
-  bool bad = (rrcount <= 0 || (unsigned int)rrcount > hostdb_round_robin_max_count || good <= 0 ||
-              (unsigned int)good > hostdb_round_robin_max_count);
-  if (bad) {
-    ink_assert(!"bad round robin size");
-    return nullptr;
-  }
-
-  uint32_t key = makeHostHash(target);
-  for (int i = 0; i < good; i++) {
-    if (info(i).data.srv.key == key && !strcmp(target, info(i).srvname(this)))
-      return &info(i);
-  }
-  return nullptr;
-}
-
-inline HostDBInfo *
-HostDBRoundRobin::select_best_http(sockaddr const *client_ip, ink_time_t now, int32_t fail_window)
-{
-  bool bad = (rrcount <= 0 || (unsigned int)rrcount > hostdb_round_robin_max_count || good <= 0 ||
-              (unsigned int)good > hostdb_round_robin_max_count);
-
-  if (bad) {
-    ink_assert(!"bad round robin size");
-    return nullptr;
-  }
-
-  int best_any = 0;
-  int best_up  = -1;
-
-  // Basic round robin, increment current and mod with how many we have
-  if (HostDBProcessor::hostdb_strict_round_robin) {
-    Debug("hostdb", "Using strict round robin");
-    // Check that the host we selected is alive
-    for (int i = 0; i < good; i++) {
-      best_any = current++ % good;
-      if (info(best_any).is_alive(now, fail_window)) {
-        best_up = best_any;
-        break;
-      }
-    }
-  } else if (HostDBProcessor::hostdb_timed_round_robin > 0) {
-    Debug("hostdb", "Using timed round-robin for HTTP");
-    if ((now - timed_rr_ctime) > HostDBProcessor::hostdb_timed_round_robin) {
-      Debug("hostdb", "Timed interval expired.. rotating");
-      ++current;
-      timed_rr_ctime = now;
-    }
-    for (int i = 0; i < good; i++) {
-      best_any = (current + i) % good;
-      if (info(best_any).is_alive(now, fail_window)) {
-        best_up = best_any;
-        break;
-      }
-    }
-    Debug("hostdb", "Using %d for best_up", best_up);
-  } else {
-    Debug("hostdb", "Using default round robin");
-    unsigned int best_hash_any = 0;
-    unsigned int best_hash_up  = 0;
-    for (int i = 0; i < good; i++) {
-      sockaddr const *ip = info(i).ip();
-      unsigned int h     = HOSTDB_CLIENT_IP_HASH(client_ip, ip);
-      if (best_hash_any <= h) {
-        best_any      = i;
-        best_hash_any = h;
-      }
-      if (info(i).is_alive(now, fail_window)) {
-        if (best_hash_up <= h) {
-          best_up      = i;
-          best_hash_up = h;
-        }
-      }
-    }
-  }
-
-  if (best_up != -1) {
-    ink_assert(best_up >= 0 && best_up < good);
-    return &info(best_up);
-  } else {
-    ink_assert(best_any >= 0 && best_any < good);
-    return &info(best_any);
-  }
-}
-
-inline HostDBInfo *
-HostDBRoundRobin::select_best_srv(char *target, InkRand *rand, ink_time_t now, int32_t fail_window)
-{
-  bool bad = (rrcount <= 0 || (unsigned int)rrcount > hostdb_round_robin_max_count || good <= 0 ||
-              (unsigned int)good > hostdb_round_robin_max_count);
-
-  if (bad) {
-    ink_assert(!"bad round robin size");
-    return nullptr;
-  }
-
-#ifdef DEBUG
-  for (int i = 1; i < good; ++i) {
-    ink_assert(info(i).data.srv.srv_priority >= info(i - 1).data.srv.srv_priority);
-  }
-#endif
-
-  int i           = 0;
-  int len         = 0;
-  uint32_t weight = 0, p = INT32_MAX;
-  HostDBInfo *result = nullptr;
-  HostDBInfo *infos[good];
-  do {
-    // if the real isn't alive-- exclude it from selection
-    if (!info(i).is_alive(now, fail_window)) {
-      continue;
-    }
-
-    if (info(i).data.srv.srv_priority <= p) {
-      p = info(i).data.srv.srv_priority;
-      weight += info(i).data.srv.srv_weight;
-      infos[len++] = &info(i);
-    } else
-      break;
-  } while (++i < good);
-
-  if (len == 0) { // all failed
-    result = &info(current++ % good);
-  } else if (weight == 0) { // srv weight is 0
-    result = infos[current++ % len];
-  } else {
-    uint32_t xx = rand->random() % weight;
-    for (i = 0; i < len - 1 && xx >= infos[i]->data.srv.srv_weight; ++i)
-      xx -= infos[i]->data.srv.srv_weight;
-
-    result = infos[i];
-  }
 
-  if (result) {
-    ink_strlcpy(target, result->srvname(this), MAXDNAME);
-    return result;
-  }
-  return nullptr;
-}
+  std::shared_ptr<HostFileMap> acquire_host_file();
+};
 
 //
 // Types
@@ -398,10 +193,9 @@ struct HostDBHash {
 
   CryptoHash hash; ///< The hash value.
 
-  const char *host_name = nullptr; ///< Host name.
-  int host_len          = 0;       ///< Length of @a _host_name
-  IpAddr ip;                       ///< IP address.
-  in_port_t port = 0;              ///< IP port (host order).
+  ts::TextView host_name; ///< Name of the host for the query.
+  IpAddr ip;              ///< IP address.
+  in_port_t port = 0;     ///< IP port (host order).
   /// DNS server. Not strictly part of the hash data but
   /// it's both used by @c HostDBContinuation and provides access to
   /// hash data. It's just handier to store it here for both uses.
@@ -418,20 +212,23 @@ struct HostDBHash {
   /** Assign a hostname.
       This updates the split DNS data as well.
   */
-  self &set_host(const char *name, int len);
+  self &set_host(ts::TextView name);
+  self &
+  set_host(char const *name)
+  {
+    return this->set_host(ts::TextView{name, strlen(name)});
+  }
 };
 
 //
 // Handles a HostDB lookup request
 //
-struct HostDBContinuation;
-typedef int (HostDBContinuation::*HostDBContHandler)(int, void *);
+using HostDBContHandler = int (HostDBContinuation::*)(int, void *);
 
 struct HostDBContinuation : public Continuation {
   Action action;
   HostDBHash hash;
-  //  IpEndpoint ip;
-  unsigned int ttl = 0;
+  ts_seconds ttl{0};
   //  HostDBMark db_mark; ///< Target type.
   /// Original IP address family style. Note this will disagree with
   /// @a hash.db_mark when doing a retry on an alternate family. The retry
@@ -440,9 +237,8 @@ struct HostDBContinuation : public Continuation {
   int dns_lookup_timeout      = DEFAULT_OPTIONS.timeout;
   Event *timeout              = nullptr;
   Continuation *from_cont     = nullptr;
-  HostDBApplicationInfo app;
-  int probe_depth            = 0;
-  size_t current_iterate_pos = 0;
+  int probe_depth             = 0;
+  size_t current_iterate_pos  = 0;
   //  char name[MAXDNAME];
   //  int namelen;
   char hash_host_name_store[MAXDNAME + 1]; // used as backing store for @a hash
@@ -452,7 +248,6 @@ struct HostDBContinuation : public Continuation {
 
   unsigned int missing : 1;
   unsigned int force_dns : 1;
-  unsigned int round_robin : 1;
 
   int probeEvent(int event, Event *e);
   int iterateEvent(int event, Event *e);
@@ -475,19 +270,23 @@ struct HostDBContinuation : public Continuation {
   {
     return hash.db_mark == HOSTDB_MARK_SRV;
   }
-  HostDBInfo *lookup_done(IpAddr const &ip, const char *aname, bool round_robin, unsigned int attl, SRVHosts *s = nullptr,
-                          HostDBInfo *r = nullptr);
+
+  Ptr<HostDBRecord>
+  lookup_done(const char *query_name, ts_seconds answer_ttl, SRVHosts *s = nullptr, Ptr<HostDBRecord> record = Ptr<HostDBRecord>{})
+  {
+    return this->lookup_done(ts::TextView{query_name, strlen(query_name)}, answer_ttl, s, record);
+  }
+
+  Ptr<HostDBRecord> lookup_done(ts::TextView query_name, ts_seconds answer_ttl, SRVHosts *s = nullptr,
+                                Ptr<HostDBRecord> record = Ptr<HostDBRecord>{});
+
   int key_partition();
-  void remove_trigger_pending_dns();
+  void remove_and_trigger_pending_dns();
   int set_check_pending_dns();
 
-  HostDBInfo *insert(unsigned int attl);
-
   /** Optional values for @c init.
    */
   struct Options {
-    typedef Options self; ///< Self reference type.
-
     int timeout                 = 0;             ///< Timeout value. Default 0
     HostResStyle host_res_style = HOST_RES_NONE; ///< IP address family fallback. Default @c HOST_RES_NONE
     bool force_dns              = false;         ///< Force DNS lookup. Default @c false
@@ -500,7 +299,7 @@ struct HostDBContinuation : public Continuation {
   int make_get_message(char *buf, int len);
   int make_put_message(HostDBInfo *r, Continuation *c, char *buf, int len);
 
-  HostDBContinuation() : missing(false), force_dns(DEFAULT_OPTIONS.force_dns), round_robin(false)
+  HostDBContinuation() : missing(false), force_dns(DEFAULT_OPTIONS.force_dns)
   {
     ink_zero(hash_host_name_store);
     ink_zero(hash.hash);
@@ -514,12 +313,6 @@ master_hash(CryptoHash const &hash)
   return static_cast<int>(hash[1] >> 32);
 }
 
-inline bool
-is_dotted_form_hostname(const char *c)
-{
-  return -1 != (int)ink_inet_addr(c);
-}
-
 inline Queue<HostDBContinuation> &
 HostDBCache::pending_dns_for_hash(const CryptoHash &hash)
 {
diff --git a/plugins/lua/ts_lua_misc.c b/plugins/lua/ts_lua_misc.c
index 8859a17ba..f8848f045 100644
--- a/plugins/lua/ts_lua_misc.c
+++ b/plugins/lua/ts_lua_misc.c
@@ -517,12 +517,14 @@ ts_lua_host_lookup_handler(TSCont contp, TSEvent event, void *edata)
   } else if (!edata) {
     lua_pushnil(L);
   } else {
-    TSHostLookupResult result   = (TSHostLookupResult)edata;
-    struct sockaddr const *addr = TSHostLookupResultAddrGet(result);
+    TSHostLookupResult record   = (TSHostLookupResult)edata;
+    struct sockaddr const *addr = TSHostLookupResultAddrGet(record);
     if (addr->sa_family == AF_INET) {
-      inet_ntop(AF_INET, (const void *)&((struct sockaddr_in *)addr)->sin_addr, cip, sizeof(cip));
+      inet_ntop(AF_INET, &((struct sockaddr_in const *)addr)->sin_addr, cip, sizeof(cip));
+    } else if (addr->sa_family == AF_INET6) {
+      inet_ntop(AF_INET6, &((struct sockaddr_in6 const *)addr)->sin6_addr, cip, sizeof(cip));
     } else {
-      inet_ntop(AF_INET6, (const void *)&((struct sockaddr_in6 *)addr)->sin6_addr, cip, sizeof(cip));
+      cip[0] = 0;
     }
     lua_pushstring(L, cip);
   }
diff --git a/proxy/ControlMatcher.h b/proxy/ControlMatcher.h
index 6c1d36244..6365ee0ed 100644
--- a/proxy/ControlMatcher.h
+++ b/proxy/ControlMatcher.h
@@ -144,10 +144,10 @@ public:
     ink_zero(dest_ip);
   }
 
-  HTTPHdr *hdr          = nullptr;
-  char *hostname_str    = nullptr;
-  HttpApiInfo *api_info = nullptr;
-  time_t xact_start     = 0;
+  HTTPHdr *hdr             = nullptr;
+  char const *hostname_str = nullptr;
+  HttpApiInfo *api_info    = nullptr;
+  time_t xact_start        = 0;
   IpEndpoint src_ip;
   IpEndpoint dest_ip;
   uint16_t incoming_port                = 0;
diff --git a/proxy/ParentSelection.cc b/proxy/ParentSelection.cc
index 0f495f1af..9d5546e2e 100644
--- a/proxy/ParentSelection.cc
+++ b/proxy/ParentSelection.cc
@@ -1068,21 +1068,21 @@ EXCLUSIVE_REGRESSION_TEST(PARENTSELECTION)(RegressionTest * /* t ATS_UNUSED */,
     params = new ParentConfigParams(ParentTable);                                                                          \
   } while (0)
 
-#define REINIT                             \
-  do {                                     \
-    if (request != NULL) {                 \
-      delete request->hdr;                 \
-      ats_free(request->hostname_str);     \
-      delete request->api_info;            \
-    }                                      \
-    delete request;                        \
-    delete result;                         \
-    request = new HttpRequestData();       \
-    result  = new ParentResult();          \
-    if (!result || !request) {             \
-      (void)printf("Allocation failed\n"); \
-      return;                              \
-    }                                      \
+#define REINIT                               \
+  do {                                       \
+    if (request != nullptr) {                \
+      delete request->hdr;                   \
+      /* ats_free(request->hostname_str); */ \
+      delete request->api_info;              \
+    }                                        \
+    delete request;                          \
+    delete result;                           \
+    request = new HttpRequestData();         \
+    result  = new ParentResult();            \
+    if (!result || !request) {               \
+      (void)printf("Allocation failed\n");   \
+      return;                                \
+    }                                        \
   } while (0)
 
 #define ST(x)                                    \
diff --git a/proxy/http/HttpConfig.cc b/proxy/http/HttpConfig.cc
index d7798dbb1..7fe46cef7 100644
--- a/proxy/http/HttpConfig.cc
+++ b/proxy/http/HttpConfig.cc
@@ -35,6 +35,8 @@
 #include <records/I_RecHttp.h>
 #include "HttpSessionManager.h"
 
+extern void HostDB_Config_Init();
+
 #define HttpEstablishStaticConfigStringAlloc(_ix, _n) \
   REC_EstablishStaticConfigStringAlloc(_ix, _n);      \
   REC_RegisterConfigUpdateFunc(_n, http_config_cb, NULL)
@@ -1372,7 +1374,7 @@ HttpConfig::startup()
   HttpEstablishStaticConfigByte(c.referer_filter_enabled, "proxy.config.http.referer_filter");
   HttpEstablishStaticConfigByte(c.referer_format_redirect, "proxy.config.http.referer_format_redirect");
 
-  HttpEstablishStaticConfigLongLong(c.oride.down_server_timeout, "proxy.config.http.down_server.cache_time");
+  HostDB_Config_Init();
 
   // Negative caching and revalidation
   HttpEstablishStaticConfigByte(c.oride.negative_caching_enabled, "proxy.config.http.negative_caching_enabled");
diff --git a/proxy/http/HttpConfig.h b/proxy/http/HttpConfig.h
index 03c4904a0..cc6b3e2e9 100644
--- a/proxy/http/HttpConfig.h
+++ b/proxy/http/HttpConfig.h
@@ -40,6 +40,8 @@
 #include <cctype>
 #include <string_view>
 
+#include <chrono>
+
 #include "tscore/ink_platform.h"
 #include "tscore/ink_inet.h"
 #include "tscore/ink_resolver.h"
@@ -688,7 +690,7 @@ struct OverridableHttpConfigParams {
   MgmtByte enable_parent_timeout_markdowns = 0;
   MgmtByte disable_parent_markdowns        = 0;
 
-  MgmtInt down_server_timeout    = 300;
+  ts_seconds down_server_timeout{300};
   MgmtInt client_abort_threshold = 1000;
 
   // open read failure retries.
diff --git a/proxy/http/HttpConnectionCount.cc b/proxy/http/HttpConnectionCount.cc
index cd64e8d38..009ea8f33 100644
--- a/proxy/http/HttpConnectionCount.cc
+++ b/proxy/http/HttpConnectionCount.cc
@@ -31,7 +31,7 @@
 
 using namespace std::literals;
 
-extern int http_config_cb(const char *, RecDataT, RecData, void *);
+extern void Enable_Config_Var(std::string_view const &name, bool (*cb)(const char *, RecDataT, RecData, void *), void *cookie);
 
 OutboundConnTrack::Imp OutboundConnTrack::_imp;
 
diff --git a/proxy/http/HttpSM.cc b/proxy/http/HttpSM.cc
index ded644801..813b6032c 100644
--- a/proxy/http/HttpSM.cc
+++ b/proxy/http/HttpSM.cc
@@ -1867,7 +1867,7 @@ HttpSM::state_http_server_open(int event, void *data)
     NetVConnection *netvc        = static_cast<NetVConnection *>(data);
     UnixNetVConnection *vc       = static_cast<UnixNetVConnection *>(data);
     PoolableSession *new_session = this->create_server_session(netvc);
-    if (t_state.current.request_to == HttpTransact::PARENT_PROXY) {
+    if (t_state.current.request_to == ResolveInfo::PARENT_PROXY) {
       new_session->to_parent_proxy = true;
       HTTP_INCREMENT_DYN_STAT(http_current_parent_proxy_connections_stat);
       HTTP_INCREMENT_DYN_STAT(http_total_parent_proxy_connections_stat);
@@ -2230,34 +2230,28 @@ HttpSM::state_send_server_request_header(int event, void *data)
 }
 
 void
-HttpSM::process_srv_info(HostDBInfo *r)
+HttpSM::process_srv_info(HostDBRecord *record)
 {
   SMDebug("dns_srv", "beginning process_srv_info");
-  t_state.hostdb_entry = Ptr<HostDBInfo>(r);
+  t_state.dns_info.record = record;
 
   /* we didn't get any SRV records, continue w normal lookup */
-  if (!r || !r->is_srv || !r->round_robin) {
-    t_state.dns_info.srv_hostname[0]    = '\0';
-    t_state.dns_info.srv_lookup_success = false;
-    t_state.my_txn_conf().srv_enabled   = false;
+  if (!record || !record->is_srv()) {
+    t_state.dns_info.srv_hostname[0]  = '\0';
+    t_state.dns_info.resolved_p       = false;
+    t_state.my_txn_conf().srv_enabled = false;
     SMDebug("dns_srv", "No SRV records were available, continuing to lookup %s", t_state.dns_info.lookup_name);
   } else {
-    HostDBRoundRobin *rr = r->rr();
-    HostDBInfo *srv      = nullptr;
-    if (rr) {
-      srv = rr->select_best_srv(t_state.dns_info.srv_hostname, &mutex->thread_holding->generator, ink_local_time(),
-                                static_cast<int>(t_state.txn_conf->down_server_timeout));
-    }
+    HostDBInfo *srv = record->select_best_srv(t_state.dns_info.srv_hostname, &mutex->thread_holding->generator, ts_clock::now(),
+                                              t_state.txn_conf->down_server_timeout);
     if (!srv) {
-      t_state.dns_info.srv_lookup_success = false;
-      t_state.dns_info.srv_hostname[0]    = '\0';
-      t_state.my_txn_conf().srv_enabled   = false;
+      //      t_state.dns_info.srv_lookup_success = false;
+      t_state.dns_info.srv_hostname[0]  = '\0';
+      t_state.my_txn_conf().srv_enabled = false;
       SMDebug("dns_srv", "SRV records empty for %s", t_state.dns_info.lookup_name);
     } else {
-      t_state.dns_info.srv_lookup_success = true;
-      t_state.dns_info.srv_port           = srv->data.srv.srv_port;
-      t_state.dns_info.srv_app            = srv->app;
-      // t_state.dns_info.single_srv = (rr->good == 1);
+      t_state.dns_info.resolved_p = false;
+      t_state.dns_info.srv_port   = srv->data.srv.srv_port;
       ink_assert(srv->data.srv.key == makeHostHash(t_state.dns_info.srv_hostname));
       SMDebug("dns_srv", "select SRV records %s", t_state.dns_info.srv_hostname);
     }
@@ -2266,87 +2260,43 @@ HttpSM::process_srv_info(HostDBInfo *r)
 }
 
 void
-HttpSM::process_hostdb_info(HostDBInfo *r)
+HttpSM::process_hostdb_info(HostDBRecord *record)
 {
-  // Increment the refcount to our item, since we are pointing at it
-  t_state.hostdb_entry = Ptr<HostDBInfo>(r);
+  t_state.dns_info.record = record; // protect record.
+
+  bool use_client_addr = t_state.http_config_param->use_client_target_addr == 1 && t_state.client_info.is_transparent &&
+                         t_state.dns_info.os_addr_style == ResolveInfo::OS_Addr::TRY_DEFAULT;
+
+  t_state.dns_info.set_active(nullptr);
 
-  sockaddr const *client_addr = nullptr;
-  bool use_client_addr        = t_state.http_config_param->use_client_target_addr == 1 && t_state.client_info.is_transparent &&
-                         t_state.dns_info.os_addr_style == HttpTransact::DNSLookupInfo::OS_Addr::OS_ADDR_TRY_DEFAULT;
   if (use_client_addr) {
     NetVConnection *vc = ua_txn ? ua_txn->get_netvc() : nullptr;
     if (vc) {
-      client_addr = vc->get_local_addr();
-      // Regardless of whether the client address matches the DNS record or not,
-      // we want to use that address.  Therefore, we copy over the client address
-      // info and skip the assignment from the DNS cache
-      ats_ip_copy(t_state.host_db_info.ip(), client_addr);
-      t_state.dns_info.os_addr_style  = HttpTransact::DNSLookupInfo::OS_Addr::OS_ADDR_TRY_CLIENT;
-      t_state.dns_info.lookup_success = true;
-      // Leave ret unassigned, so we don't overwrite the host_db_info
-    } else {
-      use_client_addr = false;
+      t_state.dns_info.set_upstream_address(vc->get_local_addr());
+      t_state.dns_info.os_addr_style = ResolveInfo::OS_Addr::TRY_CLIENT;
     }
   }
 
-  if (r && !r->is_failed()) {
-    ink_time_t now                    = ink_local_time();
-    HostDBInfo *ret                   = nullptr;
-    t_state.dns_info.lookup_success   = true;
-    t_state.dns_info.lookup_validated = true;
-
-    HostDBRoundRobin *rr = r->round_robin ? r->rr() : nullptr;
-    if (rr) {
+  if (record && !record->is_failed()) {
+    t_state.dns_info.inbound_remote_addr = &t_state.client_info.src_addr.sa;
+    if (!use_client_addr) {
+      t_state.dns_info.set_active(
+        record->select_best_http(ts_clock::now(), t_state.txn_conf->down_server_timeout, t_state.dns_info.inbound_remote_addr));
+    } else {
       // if use_client_target_addr is set, make sure the client addr is in the results pool
-      if (use_client_addr && rr->find_ip(client_addr) == nullptr) {
+      t_state.dns_info.cta_validated_p = true;
+      t_state.dns_info.record          = record; // Cache this but do not make it active.
+      if (record->find(t_state.dns_info.addr) == nullptr) {
         SMDebug("http", "use_client_target_addr == 1. Client specified address is not in the pool, not validated.");
-        t_state.dns_info.lookup_validated = false;
-      } else {
-        // Since the time elapsed between current time and client_request_time
-        // may be very large, we cannot use client_request_time to approximate
-        // current time when calling select_best_http().
-        ret = rr->select_best_http(&t_state.client_info.src_addr.sa, now, static_cast<int>(t_state.txn_conf->down_server_timeout));
-        // set the srv target`s last_failure
-        if (t_state.dns_info.srv_lookup_success) {
-          uint32_t last_failure = 0xFFFFFFFF;
-          for (int i = 0; i < rr->rrcount && last_failure != 0; ++i) {
-            if (last_failure > rr->info(i).app.http_data.last_failure) {
-              last_failure = rr->info(i).app.http_data.last_failure;
-            }
-          }
-
-          if (last_failure != 0 && static_cast<uint32_t>(now - t_state.txn_conf->down_server_timeout) < last_failure) {
-            HostDBApplicationInfo app;
-            app.allotment.application1 = 0;
-            app.allotment.application2 = 0;
-            app.http_data.last_failure = last_failure;
-            hostDBProcessor.setby_srv(t_state.dns_info.lookup_name, 0, t_state.dns_info.srv_hostname, &app);
-          }
-        }
-      }
-    } else {
-      if (use_client_addr && !ats_ip_addr_eq(client_addr, &r->data.ip.sa)) {
-        SMDebug("http", "use_client_target_addr == 1. Comparing single addresses failed, not validated.");
-        t_state.dns_info.lookup_validated = false;
-      } else {
-        ret = r;
+        t_state.dns_info.cta_validated_p = false;
       }
     }
-    if (ret) {
-      t_state.host_db_info = *ret;
-      ink_release_assert(!t_state.host_db_info.reverse_dns);
-      ink_release_assert(ats_is_ip(t_state.host_db_info.ip()));
-    }
   } else {
     SMDebug("http", "DNS lookup failed for '%s'", t_state.dns_info.lookup_name);
+  }
 
-    if (!use_client_addr) {
-      t_state.dns_info.lookup_success = false;
-    }
-    t_state.host_db_info.app.allotment.application1 = 0;
-    t_state.host_db_info.app.allotment.application2 = 0;
-    ink_assert(!t_state.host_db_info.round_robin);
+  if (!t_state.dns_info.resolved_p) {
+    SMDebug("http", "[%" PRId64 "] resolution failed for '%s'", sm_id, t_state.dns_info.lookup_name);
   }
 
   milestones[TS_MILESTONE_DNS_LOOKUP_END] = Thread::get_hrtime();
@@ -2359,6 +2309,13 @@ HttpSM::process_hostdb_info(HostDBInfo *r)
   }
 }
 
+int
+HttpSM::state_pre_resolve(int event, void *data)
+{
+  STATE_ENTER(&HttpSM::state_hostdb_lookup, event);
+  return 0;
+}
+
 //////////////////////////////////////////////////////////////////////////////
 //
 //  HttpSM::state_hostdb_lookup()
@@ -2377,16 +2334,16 @@ HttpSM::state_hostdb_lookup(int event, void *data)
   switch (event) {
   case EVENT_HOST_DB_LOOKUP:
     pending_action = nullptr;
-    process_hostdb_info(static_cast<HostDBInfo *>(data));
+    process_hostdb_info(static_cast<HostDBRecord *>(data));
     call_transact_and_set_next_state(nullptr);
     break;
   case EVENT_SRV_LOOKUP: {
     pending_action = nullptr;
-    process_srv_info(static_cast<HostDBInfo *>(data));
+    process_srv_info(static_cast<HostDBRecord *>(data));
 
-    char *host_name = t_state.dns_info.srv_lookup_success ? t_state.dns_info.srv_hostname : t_state.dns_info.lookup_name;
+    char const *host_name = t_state.dns_info.is_srv() ? t_state.dns_info.record->name() : t_state.dns_info.lookup_name;
     HostDBProcessor::Options opt;
-    opt.port  = t_state.dns_info.srv_lookup_success ? t_state.dns_info.srv_port : t_state.server_info.dst_addr.host_order_port();
+    opt.port  = t_state.dns_info.is_srv() ? t_state.dns_info.srv_port : t_state.server_info.dst_addr.host_order_port();
     opt.flags = (t_state.cache_info.directives.does_client_permit_dns_storing) ? HostDBProcessor::HOSTDB_DO_NOT_FORCE_DNS :
                                                                                  HostDBProcessor::HOSTDB_FORCE_DNS_RELOAD;
     opt.timeout        = (t_state.api_txn_dns_timeout_value != -1) ? t_state.api_txn_dns_timeout_value : 0;
@@ -2421,7 +2378,7 @@ HttpSM::state_hostdb_reverse_lookup(int event, void *data)
   case EVENT_HOST_DB_LOOKUP:
     pending_action = nullptr;
     if (data) {
-      t_state.request_data.hostname_str = (static_cast<HostDBInfo *>(data))->hostname();
+      t_state.request_data.hostname_str = (static_cast<HostDBRecord *>(data))->name();
     } else {
       SMDebug("http", "reverse DNS lookup failed for '%s'", t_state.dns_info.lookup_name);
     }
@@ -2443,27 +2400,15 @@ int
 HttpSM::state_mark_os_down(int event, void *data)
 {
   STATE_ENTER(&HttpSM::state_mark_os_down, event);
-  HostDBInfo *mark_down = nullptr;
 
   if (event == EVENT_HOST_DB_LOOKUP && data) {
-    HostDBInfo *r = static_cast<HostDBInfo *>(data);
-
-    if (r->round_robin) {
-      // Look for the entry we need mark down in the round robin
-      ink_assert(t_state.current.server != nullptr);
-      ink_assert(t_state.current.request_to == HttpTransact::ORIGIN_SERVER);
-      if (t_state.current.server) {
-        mark_down = r->rr()->find_ip(&t_state.current.server->dst_addr.sa);
-      }
-    } else {
-      // No longer a round robin, check to see if our address is the same
-      if (ats_ip_addr_eq(t_state.host_db_info.ip(), r->ip())) {
-        mark_down = r;
-      }
-    }
+    auto r = static_cast<HostDBRecord *>(data);
 
-    if (mark_down) {
-      mark_host_failure(mark_down, t_state.request_sent_time);
+    // Look for the entry we need mark down in the round robin
+    ink_assert(t_state.current.server != nullptr);
+    ink_assert(t_state.dns_info.looking_up == ResolveInfo::ORIGIN_SERVER);
+    if (auto *info = r->find(&t_state.dns_info.addr.sa); info != nullptr) {
+      info->mark_down(ts_clock::now());
     }
   }
   // We either found our entry or we did not.  Either way find
@@ -4260,8 +4205,8 @@ HttpSM::do_hostdb_lookup()
     }
     pending_action = hostDBProcessor.getSRVbyname_imm(this, (cb_process_result_pfn)&HttpSM::process_srv_info, d, 0, opt);
     if (pending_action.empty()) {
-      char *host_name = t_state.dns_info.srv_lookup_success ? t_state.dns_info.srv_hostname : t_state.dns_info.lookup_name;
-      opt.port        = t_state.dns_info.srv_lookup_success ?
+      char const *host_name = t_state.dns_info.resolved_p ? t_state.dns_info.srv_hostname : t_state.dns_info.lookup_name;
+      opt.port              = t_state.dns_info.resolved_p ?
                    t_state.dns_info.srv_port :
                    t_state.server_info.dst_addr.isValid() ? t_state.server_info.dst_addr.host_order_port() :
                                                             t_state.hdr_info.client_request.port_get();
@@ -4347,18 +4292,10 @@ HttpSM::track_connect_fail() const
 void
 HttpSM::do_hostdb_update_if_necessary()
 {
-  int issue_update = 0;
-
-  if (t_state.current.server == nullptr || plugin_tunnel_type != HTTP_NO_PLUGIN_TUNNEL) {
+  if (t_state.current.server == nullptr || plugin_tunnel_type != HTTP_NO_PLUGIN_TUNNEL || t_state.dns_info.active == nullptr) {
     // No server, so update is not necessary
     return;
   }
-  // If we failed back over to the origin server, we don't have our
-  //   hostdb information anymore which means we shouldn't update the hostdb
-  if (!ats_ip_addr_eq(&t_state.current.server->dst_addr.sa, t_state.host_db_info.ip())) {
-    SMDebug("http", "skipping hostdb update due to server failover");
-    return;
-  }
 
   if (t_state.updated_server_version != HTTP_INVALID) {
     // we may have incorrectly assumed that the hostdb had the wrong version of
@@ -4368,39 +4305,25 @@ HttpSM::do_hostdb_update_if_necessary()
     //
     // This test therefore just issues the update only if the hostdb version is
     // in fact different from the version we want the value to be updated to.
-    if (t_state.host_db_info.app.http_data.http_version != t_state.updated_server_version) {
-      t_state.host_db_info.app.http_data.http_version = t_state.updated_server_version;
-      issue_update |= 1;
-    }
-
-    t_state.updated_server_version = HTTP_INVALID;
+    t_state.updated_server_version        = HTTP_INVALID;
+    t_state.dns_info.active->http_version = t_state.updated_server_version;
   }
+
   // Check to see if we need to report or clear a connection failure
   if (track_connect_fail()) {
-    issue_update |= 1;
-    mark_host_failure(&t_state.host_db_info, t_state.client_request_time);
+    this->mark_host_failure(&t_state.dns_info, ts_clock::from_time_t(t_state.client_request_time));
   } else {
-    if (t_state.host_db_info.app.http_data.last_failure != 0) {
-      t_state.host_db_info.app.http_data.last_failure = 0;
-      t_state.host_db_info.app.http_data.fail_count   = 0;
-      issue_update |= 1;
-      char addrbuf[INET6_ADDRPORTSTRLEN];
-      SMDebug("http", "hostdb update marking IP: %s as up",
-              ats_ip_nptop(&t_state.current.server->dst_addr.sa, addrbuf, sizeof(addrbuf)));
-    }
-
-    if (t_state.dns_info.srv_lookup_success && t_state.dns_info.srv_app.http_data.last_failure != 0) {
-      t_state.dns_info.srv_app.http_data.last_failure = 0;
-      hostDBProcessor.setby_srv(t_state.dns_info.lookup_name, 0, t_state.dns_info.srv_hostname, &t_state.dns_info.srv_app);
-      SMDebug("http", "hostdb update marking SRV: %s as up", t_state.dns_info.srv_hostname);
+    if (t_state.dns_info.mark_active_server_alive()) {
+      if (t_state.dns_info.record->is_srv()) {
+        SMDebug("http", "[%" PRId64 "] hostdb update marking SRV: %s as up", sm_id, t_state.dns_info.record->name());
+      } else {
+        char addrbuf[INET6_ADDRPORTSTRLEN];
+        SMDebug("http", "[%" PRId64 "] hostdb update marking IP: %s as up", sm_id,
+                ats_ip_nptop(&t_state.current.server->dst_addr.sa, addrbuf, sizeof(addrbuf)));
+      }
     }
   }
 
-  if (issue_update) {
-    hostDBProcessor.setby(t_state.current.server->name, strlen(t_state.current.server->name), &t_state.current.server->dst_addr.sa,
-                          &t_state.host_db_info.app);
-  }
-
   char addrbuf[INET6_ADDRPORTSTRLEN];
   SMDebug("http", "server info = %s", ats_ip_nptop(&t_state.current.server->dst_addr.sa, addrbuf, sizeof(addrbuf)));
   return;
@@ -4898,7 +4821,7 @@ HttpSM::send_origin_throttled_response()
   // if the request is to a parent proxy, do not reset
   // t_state.current.attempts so that another parent or
   // NextHop may be tried.
-  if (t_state.current.request_to != HttpTransact::PARENT_PROXY) {
+  if (t_state.dns_info.looking_up != ResolveInfo::PARENT_PROXY) {
     t_state.current.attempts = t_state.txn_conf->connect_attempts_max_retries;
   }
   t_state.current.state = HttpTransact::OUTBOUND_CONGESTION;
@@ -5537,42 +5460,44 @@ HttpSM::do_transform_open()
 }
 
 void
-HttpSM::mark_host_failure(HostDBInfo *info, time_t time_down)
+HttpSM::mark_host_failure(ResolveInfo *info, ts_time time_down)
 {
   char addrbuf[INET6_ADDRPORTSTRLEN];
 
-  if (time_down) {
-    // Increment the fail_count
-    ++info->app.http_data.fail_count;
-    if (info->app.http_data.fail_count >= t_state.txn_conf->connect_attempts_rr_retries) {
-      if (info->app.http_data.last_failure == 0) {
-        char *url_str = t_state.hdr_info.client_request.url_string_get(&t_state.arena, nullptr);
-        int host_len;
-        const char *host_name_ptr = t_state.unmapped_url.host_get(&host_len);
-        std::string_view host_name{host_name_ptr, size_t(host_len)};
-        ts::bwprint(error_bw_buffer, "CONNECT: {::s} connecting to {} for host='{}' url='{}' marking down",
-                    ts::bwf::Errno(t_state.current.server->connect_result), t_state.current.server->dst_addr, host_name,
-                    ts::bwf::FirstOf(url_str, "<none>"));
-        Log::error("%s", error_bw_buffer.c_str());
-
-        if (url_str) {
-          t_state.arena.str_free(url_str);
+  if (info->active) {
+    if (time_down != TS_TIME_ZERO) {
+      // Increment the fail_count
+      if (++info->active->fail_count >= t_state.txn_conf->connect_attempts_rr_retries) {
+        if (info->active) {
+          if (info->active->last_failure.load() == TS_TIME_ZERO) {
+            char *url_str = t_state.hdr_info.client_request.url_string_get(&t_state.arena, nullptr);
+            int host_len;
+            const char *host_name_ptr = t_state.unmapped_url.host_get(&host_len);
+            std::string_view host_name{host_name_ptr, size_t(host_len)};
+            ts::bwprint(error_bw_buffer, "CONNECT : {::s} connecting to {} for host='{}' url='{}' marking down",
+                        ts::bwf::Errno(t_state.current.server->connect_result), t_state.current.server->dst_addr, host_name,
+                        ts::bwf::FirstOf(url_str, "<none>"));
+            Log::error("%s", error_bw_buffer.c_str());
+
+            if (url_str) {
+              t_state.arena.str_free(url_str);
+            }
+          }
+          info->active->last_failure = time_down;
+          SMDebug("http", "hostdb update marking IP: %s as down",
+                  ats_ip_nptop(&t_state.current.server->dst_addr.sa, addrbuf, sizeof(addrbuf)));
+        } else {
+          SMDebug("http", "hostdb increment IP failcount %s to %d",
+                  ats_ip_nptop(&t_state.current.server->dst_addr.sa, addrbuf, sizeof(addrbuf)), info->active->fail_count.load());
         }
+      } else { // Clear the failure
+        info->active->fail_count   = 0;
+        info->active->last_failure = time_down;
       }
-      info->app.http_data.last_failure = time_down;
-      SMDebug("http", "hostdb update marking IP: %s as down",
-              ats_ip_nptop(&t_state.current.server->dst_addr.sa, addrbuf, sizeof(addrbuf)));
-    } else {
-      SMDebug("http", "hostdb increment IP failcount %s to %d",
-              ats_ip_nptop(&t_state.current.server->dst_addr.sa, addrbuf, sizeof(addrbuf)), info->app.http_data.fail_count);
     }
-  } else { // Clear the failure
-    info->app.http_data.fail_count   = 0;
-    info->app.http_data.last_failure = time_down;
   }
-
 #ifdef DEBUG
-  ink_assert(ink_local_time() + t_state.txn_conf->down_server_timeout > time_down);
+  ink_assert(std::chrono::system_clock::now() + t_state.txn_conf->down_server_timeout > time_down);
 #endif
 }
 
@@ -5839,6 +5764,7 @@ HttpSM::handle_server_setup_error(int event, void *data)
     }
   }
 
+  [[maybe_unused]] UnixNetVConnection *dbg_vc = nullptr;
   switch (event) {
   case VC_EVENT_EOS:
     t_state.current.state = HttpTransact::CONNECTION_CLOSED;
@@ -7589,63 +7515,47 @@ HttpSM::set_next_state()
   }
 
   case HttpTransact::SM_ACTION_DNS_LOOKUP: {
-    sockaddr const *addr;
-
-    if (t_state.api_server_addr_set) {
-      /* If the API has set the server address before the OS DNS lookup
-       * then we can skip the lookup
-       */
-      ip_text_buffer ipb;
-      SMDebug("dns", "Skipping DNS lookup for API supplied target %s.",
-              ats_ip_ntop(&t_state.server_info.dst_addr, ipb, sizeof(ipb)));
-      // this seems wasteful as we will just copy it right back
-      ats_ip_copy(t_state.host_db_info.ip(), &t_state.server_info.dst_addr);
-      t_state.dns_info.lookup_success = true;
-      call_transact_and_set_next_state(nullptr);
-      break;
-    } else if (0 == ats_ip_pton(t_state.dns_info.lookup_name, t_state.host_db_info.ip()) &&
-               ats_is_ip_loopback(t_state.host_db_info.ip())) {
-      // If it's 127.0.0.1 or ::1 don't bother with hostdb
-      SMDebug("dns", "Skipping DNS lookup for %s because it's loopback", t_state.dns_info.lookup_name);
-      t_state.dns_info.lookup_success = true;
-      call_transact_and_set_next_state(nullptr);
-      break;
-    } else if (t_state.http_config_param->use_client_target_addr == 2 && !t_state.url_remap_success &&
-               t_state.parent_result.result != PARENT_SPECIFIED && t_state.client_info.is_transparent &&
-               t_state.dns_info.os_addr_style == HttpTransact::DNSLookupInfo::OS_Addr::OS_ADDR_TRY_DEFAULT &&
-               ats_is_ip(addr = ua_txn->get_netvc()->get_local_addr())) {
-      /* If the connection is client side transparent and the URL
-       * was not remapped/directed to parent proxy, we can use the
-       * client destination IP address instead of doing a DNS
-       * lookup. This is controlled by the 'use_client_target_addr'
-       * configuration parameter.
+    if (sockaddr const *addr; t_state.http_config_param->use_client_target_addr == 2 &&              // no CTA verification
+                              !t_state.url_remap_success &&                                          // wasn't remapped
+                              t_state.parent_result.result != PARENT_SPECIFIED &&                    // no parent.
+                              t_state.client_info.is_transparent &&                                  // inbound transparent
+                              t_state.dns_info.os_addr_style == ResolveInfo::OS_Addr::TRY_DEFAULT && // haven't tried anything yet.
+                              ats_is_ip(addr = ua_txn->get_netvc()->get_local_addr()))               // valid inbound remote address
+    {
+      /* If the connection is client side transparent and the URL was not remapped/directed to
+       * parent proxy, we can use the client destination IP address instead of doing a DNS lookup.
+       * This is controlled by the 'use_client_target_addr' configuration parameter.
        */
       if (is_debug_tag_set("dns")) {
         ip_text_buffer ipb;
         SMDebug("dns", "Skipping DNS lookup for client supplied target %s.", ats_ip_ntop(addr, ipb, sizeof(ipb)));
       }
-      ats_ip_copy(t_state.host_db_info.ip(), addr);
-      t_state.host_db_info.app.http_data.http_version = t_state.hdr_info.client_request.version_get();
 
-      t_state.dns_info.lookup_success = true;
-      // cache this result so we don't have to unreliably duplicate the
-      // logic later if the connect fails.
-      t_state.dns_info.os_addr_style = HttpTransact::DNSLookupInfo::OS_Addr::OS_ADDR_TRY_CLIENT;
+      t_state.dns_info.set_upstream_address(addr);
+
+      // Make a note the CTA is being used - don't do this case again.
+      t_state.dns_info.os_addr_style = ResolveInfo::OS_Addr::TRY_CLIENT;
+
+      if (t_state.hdr_info.client_request.version_get() == HTTPVersion(0, 9)) {
+        t_state.dns_info.http_version = HTTP_0_9;
+      } else if (t_state.hdr_info.client_request.version_get() == HTTPVersion(1, 0)) {
+        t_state.dns_info.http_version = HTTP_1_0;
+      } else {
+        t_state.dns_info.http_version = HTTP_1_1;
+      }
+
       call_transact_and_set_next_state(nullptr);
       break;
-    } else if (t_state.parent_result.result == PARENT_UNDEFINED && t_state.dns_info.lookup_success) {
-      // Already set, and we don't have a parent proxy to lookup
-      ink_assert(ats_is_ip(t_state.host_db_info.ip()));
-      SMDebug("dns", "Skipping DNS lookup, provided by plugin");
+    } else if (t_state.dns_info.looking_up == ResolveInfo::ORIGIN_SERVER && t_state.http_config_param->no_dns_forward_to_parent &&
+               t_state.parent_result.result != PARENT_UNDEFINED) {
+      t_state.dns_info.resolved_p = true; // seems dangerous - where's the IP address?
       call_transact_and_set_next_state(nullptr);
       break;
-    } else if (t_state.dns_info.looking_up == HttpTransact::ORIGIN_SERVER && t_state.http_config_param->no_dns_forward_to_parent &&
-               t_state.parent_result.result != PARENT_UNDEFINED) {
-      t_state.dns_info.lookup_success = true;
+    } else if (t_state.dns_info.resolve_immediate()) {
       call_transact_and_set_next_state(nullptr);
       break;
     }
-
+    // else have to do DNS.
     HTTP_SM_SET_DEFAULT_HANDLER(&HttpSM::state_hostdb_lookup);
 
     // We need to close the previous attempt
@@ -7673,7 +7583,7 @@ HttpSM::set_next_state()
       }
     }
 
-    ink_assert(t_state.dns_info.looking_up != HttpTransact::UNDEFINED_LOOKUP);
+    ink_assert(t_state.dns_info.looking_up != ResolveInfo::UNDEFINED_LOOKUP);
     do_hostdb_lookup();
     break;
   }
@@ -7858,7 +7768,7 @@ HttpSM::set_next_state()
   case HttpTransact::SM_ACTION_ORIGIN_SERVER_RR_MARK_DOWN: {
     HTTP_SM_SET_DEFAULT_HANDLER(&HttpSM::state_mark_os_down);
 
-    ink_assert(t_state.dns_info.looking_up == HttpTransact::ORIGIN_SERVER);
+    ink_assert(t_state.dns_info.looking_up == ResolveInfo::ORIGIN_SERVER);
 
     // TODO: This might not be optimal (or perhaps even correct), but it will
     // effectively mark the host as down. What's odd is that state_mark_os_down
@@ -8126,13 +8036,13 @@ HttpSM::redirect_request(const char *arg_redirect_url, const int arg_redirect_le
   t_state.response_received_time = 0;
   t_state.next_action            = HttpTransact::SM_ACTION_REDIRECT_READ;
   // we have a new OS and need to have DNS lookup the new OS
-  t_state.dns_info.lookup_success = false;
-  t_state.force_dns               = false;
+  t_state.dns_info.resolved_p = false;
+  t_state.force_dns           = false;
   t_state.server_info.clear();
   t_state.parent_info.clear();
 
   // Must reset whether the InkAPI has set the destination address
-  t_state.api_server_addr_set = false;
+  //  t_state.dns_info.api_addr_set_p = false;
 
   if (t_state.txn_conf->cache_http) {
     t_state.cache_info.object_read = nullptr;
diff --git a/proxy/http/HttpSM.h b/proxy/http/HttpSM.h
index 0d66cab71..5726f4130 100644
--- a/proxy/http/HttpSM.h
+++ b/proxy/http/HttpSM.h
@@ -261,8 +261,8 @@ public:
   // Handles the setting of all state necessary before
   //   calling transact to process the hostdb lookup
   // A NULL 'r' argument indicates the hostdb lookup failed
-  void process_hostdb_info(HostDBInfo *r);
-  void process_srv_info(HostDBInfo *r);
+  void process_hostdb_info(HostDBRecord *record);
+  void process_srv_info(HostDBRecord *record);
 
   // Called by transact.  Synchronous.
   VConnection *do_transform_open();
@@ -405,6 +405,7 @@ protected:
   int state_read_client_request_header(int event, void *data);
   int state_watch_for_client_abort(int event, void *data);
   int state_read_push_response_header(int event, void *data);
+  int state_pre_resolve(int event, void *data);
   int state_hostdb_lookup(int event, void *data);
   int state_hostdb_reverse_lookup(int event, void *data);
   int state_mark_os_down(int event, void *data);
@@ -472,7 +473,7 @@ protected:
   void handle_server_setup_error(int event, void *data);
   void handle_http_server_open();
   void handle_post_failure();
-  void mark_host_failure(HostDBInfo *info, time_t time_down);
+  void mark_host_failure(ResolveInfo *info, ts_time time_down);
   void release_server_session(bool serve_from_cache = false);
   void set_ua_abort(HttpTransact::AbortState_t ua_abort, int event);
   int write_header_into_buffer(HTTPHdr *h, MIOBuffer *b);
diff --git a/proxy/http/HttpTransact.cc b/proxy/http/HttpTransact.cc
index 3dc23eddf..9f660d420 100644
--- a/proxy/http/HttpTransact.cc
+++ b/proxy/http/HttpTransact.cc
@@ -368,7 +368,7 @@ HttpTransact::is_response_valid(State *s, HTTPHdr *incoming_response)
 inline static ParentRetry_t
 response_is_retryable(HttpTransact::State *s, HTTPStatus response_code)
 {
-  if (!HttpTransact::is_response_valid(s, &s->hdr_info.server_response) || s->current.request_to != HttpTransact::PARENT_PROXY) {
+  if (!HttpTransact::is_response_valid(s, &s->hdr_info.server_response) || s->current.request_to != ResolveInfo::PARENT_PROXY) {
     return PARENT_RETRY_NONE;
   }
   if (s->response_action.handled) {
@@ -487,8 +487,8 @@ update_cache_control_information_from_config(HttpTransact::State *s)
 bool
 HttpTransact::is_server_negative_cached(State *s)
 {
-  if (s->host_db_info.app.http_data.last_failure != 0 &&
-      s->host_db_info.app.http_data.last_failure + s->txn_conf->down_server_timeout > s->client_request_time) {
+  if (s->dns_info.active && s->dns_info.active->last_fail_time() != TS_TIME_ZERO &&
+      s->dns_info.active->last_fail_time() + s->txn_conf->down_server_timeout > ts_clock::from_time_t(s->client_request_time)) {
     return true;
   } else {
     // Make sure some nasty clock skew has not happened
@@ -496,9 +496,10 @@ HttpTransact::is_server_negative_cached(State *s)
     //   future we should tolerate bogus last failure times.  This sets
     //   the upper bound to the time that we would ever consider a server
     //   down to 2*down_server_timeout
-    if (s->client_request_time + s->txn_conf->down_server_timeout < s->host_db_info.app.http_data.last_failure) {
-      s->host_db_info.app.http_data.last_failure = 0;
-      s->host_db_info.app.http_data.fail_count   = 0;
+    if (s->dns_info.active &&
+        ts_clock::from_time_t(s->client_request_time) + s->txn_conf->down_server_timeout < s->dns_info.active->last_fail_time()) {
+      s->dns_info.active->last_failure = TS_TIME_ZERO;
+      s->dns_info.active->fail_count   = 0;
       ink_assert(!"extreme clock skew");
       return true;
     }
@@ -507,8 +508,8 @@ HttpTransact::is_server_negative_cached(State *s)
 }
 
 inline static void
-update_current_info(HttpTransact::CurrentInfo *into, HttpTransact::ConnectionAttributes *from, HttpTransact::LookingUp_t who,
-                    int attempts)
+update_current_info(HttpTransact::CurrentInfo *into, HttpTransact::ConnectionAttributes *from,
+                    ResolveInfo::UpstreamResolveStyle who, int attempts)
 {
   into->request_to = who;
   into->server     = from;
@@ -516,7 +517,7 @@ update_current_info(HttpTransact::CurrentInfo *into, HttpTransact::ConnectionAtt
 }
 
 inline static void
-update_dns_info(HttpTransact::DNSLookupInfo *dns, HttpTransact::CurrentInfo *from)
+update_dns_info(ResolveInfo *dns, HttpTransact::CurrentInfo *from)
 {
   dns->looking_up  = from->request_to;
   dns->lookup_name = from->server->name;
@@ -558,7 +559,7 @@ is_negative_caching_appropriate(HttpTransact::State *s)
   }
 }
 
-inline static HttpTransact::LookingUp_t
+inline static ResolveInfo::UpstreamResolveStyle
 find_server_and_update_current_info(HttpTransact::State *s)
 {
   int host_len;
@@ -639,16 +640,16 @@ find_server_and_update_current_info(HttpTransact::State *s)
   switch (s->parent_result.result) {
   case PARENT_SPECIFIED:
     s->parent_info.name = s->arena.str_store(s->parent_result.hostname, strlen(s->parent_result.hostname));
-    update_current_info(&s->current, &s->parent_info, HttpTransact::PARENT_PROXY, (s->current.attempts)++);
+    update_current_info(&s->current, &s->parent_info, ResolveInfo::PARENT_PROXY, (s->current.attempts)++);
     update_dns_info(&s->dns_info, &s->current);
-    ink_assert(s->dns_info.looking_up == HttpTransact::PARENT_PROXY);
+    ink_assert(s->dns_info.looking_up == ResolveInfo::PARENT_PROXY);
     s->next_hop_scheme = URL_WKSIDX_HTTP;
 
-    return HttpTransact::PARENT_PROXY;
+    return ResolveInfo::PARENT_PROXY;
   case PARENT_FAIL:
     // No more parents - need to return an error message
-    s->current.request_to = HttpTransact::HOST_NONE;
-    return HttpTransact::HOST_NONE;
+    s->current.request_to = ResolveInfo::HOST_NONE;
+    return ResolveInfo::HOST_NONE;
 
   case PARENT_DIRECT:
     // if the configuration does not allow the origin to be dns'd
@@ -656,15 +657,15 @@ find_server_and_update_current_info(HttpTransact::State *s)
     if (s->http_config_param->no_dns_forward_to_parent) {
       Warning("no available parents and the config proxy.config.http.no_dns_just_forward_to_parent, prevents origin lookups.");
       s->parent_result.result = PARENT_FAIL;
-      return HttpTransact::HOST_NONE;
+      return ResolveInfo::HOST_NONE;
     }
   /* fall through */
   default:
-    update_current_info(&s->current, &s->server_info, HttpTransact::ORIGIN_SERVER, (s->current.attempts)++);
+    update_current_info(&s->current, &s->server_info, ResolveInfo::ORIGIN_SERVER, (s->current.attempts)++);
     update_dns_info(&s->dns_info, &s->current);
-    ink_assert(s->dns_info.looking_up == HttpTransact::ORIGIN_SERVER);
+    ink_assert(s->dns_info.looking_up == ResolveInfo::ORIGIN_SERVER);
     s->next_hop_scheme = s->scheme;
-    return HttpTransact::ORIGIN_SERVER;
+    return ResolveInfo::ORIGIN_SERVER;
   }
 }
 
@@ -764,7 +765,7 @@ does_method_effect_cache(int method)
 inline static HttpTransact::StateMachineAction_t
 how_to_open_connection(HttpTransact::State *s)
 {
-  ink_assert((s->pending_work == nullptr) || (s->current.request_to == HttpTransact::PARENT_PROXY));
+  ink_assert((s->pending_work == nullptr) || (s->current.request_to == ResolveInfo::PARENT_PROXY));
 
   // Originally we returned which type of server to open
   // Now, however, we may want to issue a cache
@@ -1675,13 +1676,13 @@ HttpTransact::setup_plugin_request_intercept(State *s)
   s->scheme = s->next_hop_scheme = URL_WKSIDX_HTTP;
 
   // Set up a "fake" server entry
-  update_current_info(&s->current, &s->server_info, HttpTransact::ORIGIN_SERVER, 0);
+  update_current_info(&s->current, &s->server_info, ResolveInfo::ORIGIN_SERVER, 0);
 
   // Also "fake" the info we'd normally get from
   //   hostDB
-  s->server_info.http_version                = HTTP_1_0;
-  s->server_info.keep_alive                  = HTTP_NO_KEEPALIVE;
-  s->host_db_info.app.http_data.http_version = HTTP_1_0;
+  s->server_info.http_version = HTTP_1_0;
+  s->server_info.keep_alive   = HTTP_NO_KEEPALIVE;
+  s->server_info.http_version = HTTP_1_0;
   s->server_info.dst_addr.setToAnyAddr(AF_INET); // must set an address or we can't set the port.
   s->server_info.dst_addr.network_order_port() = htons(s->hdr_info.client_request.port_get()); // this is the info that matters.
 
@@ -1783,19 +1784,25 @@ HttpTransact::PPDNSLookup(State *s)
 {
   TxnDebug("http_trans", "Entering HttpTransact::PPDNSLookup");
 
-  ink_assert(s->dns_info.looking_up == PARENT_PROXY);
-  if (!s->dns_info.lookup_success) {
+  ink_assert(s->dns_info.looking_up == ResolveInfo::PARENT_PROXY);
+  if (!s->dns_info.resolved_p) {
     // Mark parent as down due to resolving failure
     markParentDown(s);
     // DNS lookup of parent failed, find next parent or o.s.
-    if (find_server_and_update_current_info(s) == HttpTransact::HOST_NONE) {
-      ink_assert(s->current.request_to == HOST_NONE);
+    if (find_server_and_update_current_info(s) == ResolveInfo::HOST_NONE) {
+      if (is_cache_hit(s->cache_lookup_result) && is_stale_cache_response_returnable(s)) {
+        s->source = SOURCE_CACHE;
+        TxnDebug("http_trans", "All parents are down, serving stale doc to client");
+        build_response_from_cache(s, HTTP_WARNING_CODE_REVALIDATION_FAILED);
+        return;
+      }
+      ink_assert(s->current.request_to == ResolveInfo::HOST_NONE);
       handle_parent_died(s);
       return;
     }
 
     if (!s->current.server->dst_addr.isValid()) {
-      if (s->current.request_to == PARENT_PROXY) {
+      if (s->current.request_to == ResolveInfo::PARENT_PROXY) {
         TRANSACT_RETURN(SM_ACTION_DNS_LOOKUP, PPDNSLookupAPICall);
       } else if (s->parent_result.result == PARENT_DIRECT && s->http_config_param->no_dns_forward_to_parent != 1) {
         // We ran out of parents but parent configuration allows us to go to Origin Server directly
@@ -1803,16 +1810,16 @@ HttpTransact::PPDNSLookup(State *s)
         return;
       } else {
         // We could be out of parents here if all the parents failed DNS lookup
-        ink_assert(s->current.request_to == HOST_NONE);
+        ink_assert(s->current.request_to == ResolveInfo::HOST_NONE);
         handle_parent_died(s);
       }
       return;
     }
   } else {
     // lookup succeeded, open connection to p.p.
-    ats_ip_copy(&s->parent_info.dst_addr, s->host_db_info.ip());
+    ats_ip_copy(&s->parent_info.dst_addr, s->dns_info.addr);
     s->parent_info.dst_addr.network_order_port() = htons(s->parent_result.port);
-    get_ka_info_from_host_db(s, &s->parent_info, &s->client_info, &s->host_db_info);
+    get_ka_info_from_host_db(s, &s->parent_info, &s->client_info, s->dns_info.active);
 
     char addrbuf[INET6_ADDRSTRLEN];
     TxnDebug("http_trans", "DNS lookup for successful IP: %s", ats_ip_ntop(&s->parent_info.dst_addr.sa, addrbuf, sizeof(addrbuf)));
@@ -1835,65 +1842,6 @@ HttpTransact::PPDNSLookup(State *s)
   s->next_action = how_to_open_connection(s);
 }
 
-///////////////////////////////////////////////////////////////////////////////
-//
-// Name       : ReDNSRoundRobin
-// Description: Called after we fail to contact part of a round-robin
-//              robin server set and we found a another ip address.
-//
-// Details    :
-//
-//
-//
-// Possible Next States From Here:
-// - HttpTransact::ORIGIN_SERVER_RAW_OPEN;
-// - HttpTransact::ORIGIN_SERVER_OPEN;
-// - HttpTransact::PROXY_INTERNAL_CACHE_NOOP;
-//
-///////////////////////////////////////////////////////////////////////////////
-void
-HttpTransact::ReDNSRoundRobin(State *s)
-{
-  ink_assert(s->current.server == &s->server_info);
-  ink_assert(s->current.server->had_connect_fail());
-
-  if (s->dns_info.lookup_success) {
-    // We using a new server now so clear the connection
-    //  failure mark
-    s->current.server->clear_connect_fail();
-
-    // Our ReDNS of the server succeeded so update the necessary
-    //  information and try again. Need to preserve the current port value if possible.
-    in_port_t server_port = s->current.server->dst_addr.host_order_port();
-    // Temporary check to make sure the port preservation can be depended upon. That should be the case
-    // because we get here only after trying a connection. Remove for 6.2.
-    ink_assert(s->current.server->dst_addr.isValid() && 0 != server_port);
-
-    ats_ip_copy(&s->server_info.dst_addr, s->host_db_info.ip());
-    s->server_info.dst_addr.network_order_port() = htons(server_port);
-    ats_ip_copy(&s->request_data.dest_ip, &s->server_info.dst_addr);
-    get_ka_info_from_host_db(s, &s->server_info, &s->client_info, &s->host_db_info);
-
-    char addrbuf[INET6_ADDRSTRLEN];
-    TxnDebug("http_trans", "DNS lookup for O.S. successful IP: %s",
-             ats_ip_ntop(&s->server_info.dst_addr.sa, addrbuf, sizeof(addrbuf)));
-
-    s->next_action = how_to_open_connection(s);
-  } else {
-    // Our ReDNS failed so output the DNS failure error message
-    // Set to internal server error so later logging will pick up SQUID_LOG_ERR_DNS_FAIL
-    build_error_response(s, HTTP_STATUS_INTERNAL_SERVER_ERROR, "Cannot find server.", Dns_error_body);
-    s->cache_info.action = CACHE_DO_NO_ACTION;
-    s->next_action       = SM_ACTION_SEND_ERROR_CACHE_NOOP;
-    //  s->next_action = PROXY_INTERNAL_CACHE_NOOP;
-    char *url_str = s->hdr_info.client_request.url_string_get(&s->arena, nullptr);
-    ts::bwprint(error_bw_buffer, "DNS Error: looking up {}", ts::bwf::FirstOf(url_str, "<none>"));
-    Log::error("%s", error_bw_buffer.c_str());
-  }
-
-  return;
-}
-
 ///////////////////////////////////////////////////////////////////////////////
 // Name       : OSDNSLookup
 // Description: called after the DNS lookup of origin server name
@@ -1923,29 +1871,22 @@ HttpTransact::ReDNSRoundRobin(State *s)
 void
 HttpTransact::OSDNSLookup(State *s)
 {
-  ink_assert(s->dns_info.looking_up == ORIGIN_SERVER);
+  ink_assert(s->dns_info.looking_up == ResolveInfo::UpstreamResolveStyle::ORIGIN_SERVER);
 
   TxnDebug("http_trans", "Entering HttpTransact::OSDNSLookup");
 
-  // It's never valid to connect *to* INADDR_ANY, so let's reject the request now.
-  if (ats_is_ip_any(s->host_db_info.ip())) {
-    TxnDebug("http_trans", "Invalid request IP: INADDR_ANY");
-    build_error_response(s, HTTP_STATUS_BAD_REQUEST, "Bad Destination Address", "request#syntax_error");
-    SET_VIA_STRING(VIA_DETAIL_TUNNEL, VIA_DETAIL_TUNNEL_NO_FORWARD);
-    TRANSACT_RETURN(SM_ACTION_SEND_ERROR_CACHE_NOOP, nullptr);
-  }
-
-  if (!s->dns_info.lookup_success) {
-    if (DNSLookupInfo::OS_Addr::OS_ADDR_TRY_HOSTDB == s->dns_info.os_addr_style) {
-      /*
-       *  Transparent case: We tried to connect to client target address, failed and tried to use a different addr
-       *  No HostDB data, just keep on with the CTA.
+  if (!s->dns_info.resolved_p) {
+    if (ResolveInfo::OS_Addr::TRY_HOSTDB == s->dns_info.os_addr_style) {
+      /* Transparent case: We tried to connect to client target address, failed and tried to use a different addr
+       * but that failed to resolve therefore keep on with the CTA.
        */
-      s->dns_info.lookup_success = true;
-      s->dns_info.os_addr_style  = DNSLookupInfo::OS_Addr::OS_ADDR_USE_CLIENT;
+      s->dns_info.addr.assign(s->state_machine->ua_txn->get_netvc()->get_local_addr()); // fetch CTA
+      s->dns_info.resolved_p    = true;
+      s->dns_info.os_addr_style = ResolveInfo::OS_Addr::USE_CLIENT;
       TxnDebug("http_seq", "DNS lookup unsuccessful, using client target address");
     } else {
       TxnDebug("http_seq", "DNS Lookup unsuccessful");
+      char const *log_msg;
 
       // Even with unsuccessful DNS lookup, return stale object from cache if applicable
       if (is_cache_hit(s->cache_lookup_result) && is_stale_cache_response_returnable(s)) {
@@ -1956,10 +1897,16 @@ HttpTransact::OSDNSLookup(State *s)
       }
       // output the DNS failure error message
       SET_VIA_STRING(VIA_DETAIL_TUNNEL, VIA_DETAIL_TUNNEL_NO_FORWARD);
-      // Set to internal server error so later logging will pick up SQUID_LOG_ERR_DNS_FAIL
-      build_error_response(s, HTTP_STATUS_INTERNAL_SERVER_ERROR, "Cannot find server.", "connect#dns_failed");
+      if (!s->dns_info.record || s->dns_info.record->is_failed()) {
+        // Set to internal server error so later logging will pick up SQUID_LOG_ERR_DNS_FAIL
+        build_error_response(s, HTTP_STATUS_INTERNAL_SERVER_ERROR, "Cannot find server.", "connect#dns_failed");
+        log_msg = "looking up";
+      } else {
+        build_error_response(s, HTTP_STATUS_INTERNAL_SERVER_ERROR, "No valid server.", "connect#all_dead");
+        log_msg = "no valid server";
+      }
       char *url_str = s->hdr_info.client_request.url_string_get(&s->arena, nullptr);
-      ts::bwprint(error_bw_buffer, "DNS Error: looking up {}", ts::bwf::FirstOf(url_str, "<none>"));
+      ts::bwprint(error_bw_buffer, "DNS Error: {} {}", log_msg, ts::bwf::FirstOf(url_str, "<none>"));
       Log::error("%s", error_bw_buffer.c_str());
       // s->cache_info.action = CACHE_DO_NO_ACTION;
       TRANSACT_RETURN(SM_ACTION_SEND_ERROR_CACHE_NOOP, nullptr);
@@ -1968,43 +1915,43 @@ HttpTransact::OSDNSLookup(State *s)
   }
 
   // The dns lookup succeeded
-  ink_assert(s->dns_info.lookup_success);
+  ink_assert(s->dns_info.resolved_p);
   TxnDebug("http_seq", "DNS Lookup successful");
 
+  // It's never valid to connect *to* INADDR_ANY, so let's reject the request now.
+  if (ats_is_ip_any(s->dns_info.addr)) {
+    TxnDebug("http_trans", "[OSDNSLookup] Invalid request IP: INADDR_ANY");
+    build_error_response(s, HTTP_STATUS_BAD_REQUEST, "Bad Destination Address", "request#syntax_error");
+    SET_VIA_STRING(VIA_DETAIL_TUNNEL, VIA_DETAIL_TUNNEL_NO_FORWARD);
+    TRANSACT_RETURN(SM_ACTION_SEND_ERROR_CACHE_NOOP, nullptr);
+  }
+
   // For the transparent case, nail down the kind of address we are really using
-  if (DNSLookupInfo::OS_Addr::OS_ADDR_TRY_HOSTDB == s->dns_info.os_addr_style) {
+  if (ResolveInfo::OS_Addr::TRY_HOSTDB == s->dns_info.os_addr_style) {
     // We've backed off from a client supplied address and found some
     // HostDB addresses. We use those if they're different from the CTA.
     // In all cases we now commit to client or HostDB for our source.
-    if (s->host_db_info.round_robin) {
-      HostDBInfo *cta = s->host_db_info.rr()->select_next(&s->current.server->dst_addr.sa);
-      if (cta) {
-        // found another addr, lock in host DB.
-        s->host_db_info           = *cta;
-        s->dns_info.os_addr_style = DNSLookupInfo::OS_Addr::OS_ADDR_USE_HOSTDB;
-      } else {
-        // nothing else there, continue with CTA.
-        s->dns_info.os_addr_style = DNSLookupInfo::OS_Addr::OS_ADDR_USE_CLIENT;
-      }
-    } else if (ats_ip_addr_eq(s->host_db_info.ip(), &s->server_info.dst_addr.sa)) {
-      s->dns_info.os_addr_style = DNSLookupInfo::OS_Addr::OS_ADDR_USE_CLIENT;
+    if (s->dns_info.set_active(&s->current.server->dst_addr.sa) && s->dns_info.select_next_rr()) {
+      s->dns_info.os_addr_style = ResolveInfo::OS_Addr::USE_HOSTDB;
     } else {
-      s->dns_info.os_addr_style = DNSLookupInfo::OS_Addr::OS_ADDR_USE_HOSTDB;
+      // nothing else there, continue with CTA.
+      s->dns_info.set_active(nullptr);
+      s->dns_info.set_upstream_address(&s->current.server->dst_addr.sa);
+      s->dns_info.os_addr_style = ResolveInfo::OS_Addr::USE_CLIENT;
     }
   }
 
-  // Check to see if can fullfill expect requests based on the cached
-  // update some state variables with hostdb information that has
-  // been provided.
-  ats_ip_copy(&s->server_info.dst_addr, s->host_db_info.ip());
+  s->server_info.dst_addr.assign(s->dns_info.addr);
   // If the SRV response has a port number, we should honor it. Otherwise we do the port defined in remap
-  if (s->dns_info.srv_lookup_success) {
+  if (s->dns_info.resolved_p && s->dns_info.srv_port) {
     s->server_info.dst_addr.network_order_port() = htons(s->dns_info.srv_port);
-  } else if (!s->api_server_addr_set) {
+  } else if (s->dns_info.os_addr_style == ResolveInfo::OS_Addr::USE_API && 0 != ats_ip_port_cast(s->dns_info.addr)) {
+    // Nothing - port set via API and already copied over.
+  } else {
     s->server_info.dst_addr.network_order_port() = htons(s->hdr_info.client_request.port_get()); // now we can set the port.
   }
   ats_ip_copy(&s->request_data.dest_ip, &s->server_info.dst_addr);
-  get_ka_info_from_host_db(s, &s->server_info, &s->client_info, &s->host_db_info);
+  get_ka_info_from_host_db(s, &s->server_info, &s->client_info, s->dns_info.active);
 
   char addrbuf[INET6_ADDRSTRLEN];
   TxnDebug("http_trans", "DNS lookup for O.S. successful IP: %s",
@@ -2013,14 +1960,17 @@ HttpTransact::OSDNSLookup(State *s)
   if (s->redirect_info.redirect_in_process) {
     // If dns lookup was not successful, the code below will handle the error.
     RedirectEnabled::Action action = RedirectEnabled::Action::INVALID;
-    if (true == Machine::instance()->is_self(s->host_db_info.ip())) {
+    if (true == Machine::instance()->is_self(&s->dns_info.addr.sa)) {
       action = s->http_config_param->redirect_actions_self_action;
+      TxnDebug("http_trans", "[OSDNSLookup] Self action - %d.", int(action));
     } else {
       // Make sure the return value from contains is big enough for a void*.
       intptr_t x{intptr_t(RedirectEnabled::Action::INVALID)};
       ink_release_assert(s->http_config_param->redirect_actions_map != nullptr);
-      ink_release_assert(s->http_config_param->redirect_actions_map->contains(s->host_db_info.ip(), reinterpret_cast<void **>(&x)));
+      ink_release_assert(s->http_config_param->redirect_actions_map->contains(s->dns_info.addr, reinterpret_cast<void **>(&x)));
       action = static_cast<RedirectEnabled::Action>(x);
+      TxnDebug("http_trans", "[OSDNSLookup] Mapped action - %d for family %d.", int(action),
+               int(s->dns_info.active->data.ip.family()));
     }
 
     if (action == RedirectEnabled::Action::FOLLOW) {
@@ -2055,10 +2005,11 @@ HttpTransact::OSDNSLookup(State *s)
   // After SM_ACTION_DNS_LOOKUP, goto the saved action/state ORIGIN_SERVER_(RAW_)OPEN.
   // Should we skip the StartAccessControl()? why?
 
-  if (DNSLookupInfo::OS_Addr::OS_ADDR_USE_CLIENT == s->dns_info.os_addr_style ||
-      DNSLookupInfo::OS_Addr::OS_ADDR_USE_HOSTDB == s->dns_info.os_addr_style) {
-    // we've come back after already trying the server to get a better address
-    // and finished with all backtracking - return to trying the server.
+  if (ResolveInfo::OS_Addr::USE_CLIENT == s->dns_info.os_addr_style ||
+      ResolveInfo::OS_Addr::USE_HOSTDB == s->dns_info.os_addr_style) {
+    // we've come back after already trying the server to get a better address,
+    // or we're locked on a plugin supplied address.
+    // therefore no more backtracking - return to trying the server.
     TRANSACT_RETURN(how_to_open_connection(s), HttpTransact::HandleResponse);
   } else if (s->dns_info.lookup_name[0] <= '9' && s->dns_info.lookup_name[0] >= '0' && s->parent_params->parent_table->hostMatch &&
              !s->http_config_param->no_dns_forward_to_parent) {
@@ -2233,14 +2184,14 @@ HttpTransact::LookupSkipOpenServer(State *s)
   // to a parent proxy or to the origin server.
   find_server_and_update_current_info(s);
 
-  if (s->current.request_to == PARENT_PROXY) {
+  if (s->current.request_to == ResolveInfo::PARENT_PROXY) {
     TRANSACT_RETURN(SM_ACTION_DNS_LOOKUP, PPDNSLookupAPICall);
   } else if (s->parent_result.result == PARENT_FAIL) {
     handle_parent_died(s);
     return;
   }
 
-  ink_assert(s->current.request_to == ORIGIN_SERVER);
+  ink_assert(s->current.request_to == ResolveInfo::ORIGIN_SERVER);
   // ink_assert(s->current.server->ip != 0);
 
   build_request(s, &s->hdr_info.client_request, &s->hdr_info.server_request, s->current.server->http_version);
@@ -2892,18 +2843,18 @@ HttpTransact::HandleCacheOpenReadHit(State *s)
     //  scheme & 2) If we skip down parents, every page
     //  we serve is potentially stale
     //
-    if (s->current.request_to == ORIGIN_SERVER && is_server_negative_cached(s) && response_returnable == true &&
+    if (s->current.request_to == ResolveInfo::ORIGIN_SERVER && is_server_negative_cached(s) && response_returnable == true &&
         is_stale_cache_response_returnable(s) == true) {
       server_up = false;
-      update_current_info(&s->current, nullptr, UNDEFINED_LOOKUP, 0);
+      update_current_info(&s->current, nullptr, ResolveInfo::UNDEFINED_LOOKUP, 0);
       TxnDebug("http_trans", "CacheOpenReadHit - server_down, returning stale document");
     }
     // a parent lookup could come back as PARENT_FAIL if in parent.config, go_direct == false and
     // there are no available parents (all down).
-    else if (s->current.request_to == HOST_NONE && s->parent_result.result == PARENT_FAIL) {
+    else if (s->current.request_to == ResolveInfo::HOST_NONE && s->parent_result.result == PARENT_FAIL) {
       if (response_returnable == true && is_stale_cache_response_returnable(s) == true) {
         server_up = false;
-        update_current_info(&s->current, nullptr, UNDEFINED_LOOKUP, 0);
+        update_current_info(&s->current, nullptr, ResolveInfo::UNDEFINED_LOOKUP, 0);
         TxnDebug("http_trans", "CacheOpenReadHit - server_down, returning stale document");
       } else {
         handle_parent_died(s);
@@ -2926,14 +2877,14 @@ HttpTransact::HandleCacheOpenReadHit(State *s)
           //  through.  The request will fail because of the
           //  missing ip but we won't take down the system
           //
-          if (s->current.request_to == PARENT_PROXY) {
+          if (s->current.request_to == ResolveInfo::PARENT_PROXY) {
             // Set ourselves up to handle pending revalidate issues
             //  after the PP DNS lookup
             ink_assert(s->pending_work == nullptr);
             s->pending_work = issue_revalidate;
 
             TRANSACT_RETURN(SM_ACTION_DNS_LOOKUP, PPDNSLookupAPICall);
-          } else if (s->current.request_to == ORIGIN_SERVER) {
+          } else if (s->current.request_to == ResolveInfo::ORIGIN_SERVER) {
             return CallOSDNSLookup(s);
           } else {
             handle_parent_died(s);
@@ -3352,12 +3303,12 @@ HttpTransact::HandleCacheOpenReadMiss(State *s)
       return;
     }
     if (!s->current.server->dst_addr.isValid()) {
-      ink_release_assert(s->parent_result.result == PARENT_DIRECT || s->current.request_to == PARENT_PROXY ||
+      ink_release_assert(s->parent_result.result == PARENT_DIRECT || s->current.request_to == ResolveInfo::PARENT_PROXY ||
                          s->http_config_param->no_dns_forward_to_parent != 0);
       if (s->parent_result.result == PARENT_DIRECT && s->http_config_param->no_dns_forward_to_parent != 1) {
         return CallOSDNSLookup(s);
       }
-      if (s->current.request_to == PARENT_PROXY) {
+      if (s->current.request_to == ResolveInfo::PARENT_PROXY) {
         TRANSACT_RETURN(SM_ACTION_DNS_LOOKUP, HttpTransact::PPDNSLookupAPICall);
       } else {
         handle_parent_died(s);
@@ -3466,7 +3417,7 @@ HttpTransact::HandleResponse(State *s)
 
   HTTP_INCREMENT_DYN_STAT(http_incoming_responses_stat);
 
-  ink_release_assert(s->current.request_to != UNDEFINED_LOOKUP);
+  ink_release_assert(s->current.request_to != ResolveInfo::UNDEFINED_LOOKUP);
   if (s->cache_info.action != CACHE_DO_WRITE) {
     ink_release_assert(s->cache_info.action != CACHE_DO_LOOKUP);
     ink_release_assert(s->cache_info.action != CACHE_DO_SERVE);
@@ -3483,10 +3434,10 @@ HttpTransact::HandleResponse(State *s)
   }
 
   switch (s->current.request_to) {
-  case PARENT_PROXY:
+  case ResolveInfo::PARENT_PROXY:
     handle_response_from_parent(s);
     break;
-  case ORIGIN_SERVER:
+  case ResolveInfo::ORIGIN_SERVER:
     handle_response_from_server(s);
     break;
   default:
@@ -3610,7 +3561,7 @@ HttpTransact::HandleStatPage(State *s)
 void
 HttpTransact::handle_response_from_parent(State *s)
 {
-  LookingUp_t next_lookup = UNDEFINED_LOOKUP;
+  auto next_lookup = ResolveInfo::UNDEFINED_LOOKUP;
   TxnDebug("http_trans", "(hrfp)");
   HTTP_RELEASE_ASSERT(s->current.server == &s->parent_info);
 
@@ -3717,7 +3668,7 @@ HttpTransact::handle_response_from_parent(State *s)
         markParentDown(s);
       }
       s->parent_result.result = PARENT_FAIL;
-      next_lookup             = HOST_NONE;
+      next_lookup             = ResolveInfo::HOST_NONE;
     }
     break;
   }
@@ -3725,17 +3676,17 @@ HttpTransact::handle_response_from_parent(State *s)
   // We have either tried to find a new parent or failed over to the
   //   origin server
   switch (next_lookup) {
-  case PARENT_PROXY:
-    ink_assert(s->current.request_to == PARENT_PROXY);
+  case ResolveInfo::PARENT_PROXY:
+    ink_assert(s->current.request_to == ResolveInfo::PARENT_PROXY);
     TRANSACT_RETURN(SM_ACTION_DNS_LOOKUP, PPDNSLookupAPICall);
     break;
-  case ORIGIN_SERVER:
+  case ResolveInfo::ORIGIN_SERVER:
     // Next lookup is Origin Server, try DNS for Origin Server
     return CallOSDNSLookup(s);
     break;
-  case HOST_NONE:
+  case ResolveInfo::HOST_NONE:
     // Check if content can be served from cache
-    s->current.request_to = PARENT_PROXY;
+    s->current.request_to = ResolveInfo::PARENT_PROXY;
     handle_server_connection_not_open(s);
     break;
   default:
@@ -3809,38 +3760,25 @@ HttpTransact::handle_response_from_server(State *s)
     if (is_request_retryable(s) && s->current.attempts < max_connect_retries) {
       // If this is a round robin DNS entry & we're tried configured
       //    number of times, we should try another node
-      if (DNSLookupInfo::OS_Addr::OS_ADDR_TRY_CLIENT == s->dns_info.os_addr_style) {
-        // attempt was based on client supplied server address. Try again
-        // using HostDB.
+      if (ResolveInfo::OS_Addr::TRY_CLIENT == s->dns_info.os_addr_style) {
+        // attempt was based on client supplied server address. Try again using HostDB.
         // Allow DNS attempt
-        s->dns_info.lookup_success = false;
+        s->dns_info.resolved_p = false;
         // See if we can get data from HostDB for this.
-        s->dns_info.os_addr_style = DNSLookupInfo::OS_Addr::OS_ADDR_TRY_HOSTDB;
+        s->dns_info.os_addr_style = ResolveInfo::OS_Addr::TRY_HOSTDB;
         // Force host resolution to have the same family as the client.
         // Because this is a transparent connection, we can't switch address
         // families - that is locked in by the client source address.
         ats_force_order_by_family(&s->current.server->dst_addr.sa, s->my_txn_conf().host_res_data.order);
         return CallOSDNSLookup(s);
-      } else if ((s->dns_info.srv_lookup_success || s->host_db_info.is_rr_elt()) &&
-                 (s->txn_conf->connect_attempts_rr_retries > 0) &&
-                 ((s->current.attempts + 1) % s->txn_conf->connect_attempts_rr_retries == 0)) {
-        delete_server_rr_entry(s, max_connect_retries);
-        return;
       } else {
+        if ((s->txn_conf->connect_attempts_rr_retries > 0) &&
+            ((s->current.attempts + 1) % s->txn_conf->connect_attempts_rr_retries == 0)) {
+          s->dns_info.select_next_rr();
+        }
         retry_server_connection_not_open(s, s->current.state, max_connect_retries);
         TxnDebug("http_trans", "Error. Retrying...");
         s->next_action = how_to_open_connection(s);
-
-        if (s->api_server_addr_set) {
-          // If the plugin set a server address, back up to the OS_DNS hook
-          // to let it try another one. Force OS_ADDR_USE_CLIENT so that
-          // in OSDNSLoopkup, we back up to how_to_open_connections which
-          // will tell HttpSM to connect the origin server.
-
-          s->dns_info.os_addr_style = DNSLookupInfo::OS_Addr::OS_ADDR_USE_CLIENT;
-          TRANSACT_RETURN(SM_ACTION_API_OS_DNS, OSDNSLookup);
-        }
-        return;
       }
     } else {
       error_log_connection_failure(s, s->current.state);
@@ -3863,35 +3801,6 @@ HttpTransact::handle_response_from_server(State *s)
   return;
 }
 
-///////////////////////////////////////////////////////////////////////////////
-// Name       : delete_server_rr_entry
-// Description:
-//
-// Details    :
-//
-//   connection to server failed mark down the server round robin entry
-//
-//
-// Possible Next States From Here:
-//
-///////////////////////////////////////////////////////////////////////////////
-void
-HttpTransact::delete_server_rr_entry(State *s, int max_retries)
-{
-  char addrbuf[INET6_ADDRSTRLEN];
-
-  TxnDebug("http_trans", "[%d] failed to connect to %s", s->current.attempts,
-           ats_ip_ntop(&s->current.server->dst_addr.sa, addrbuf, sizeof(addrbuf)));
-  TxnDebug("http_trans", "marking rr entry down and finding next one");
-  ink_assert(s->current.server->had_connect_fail());
-  ink_assert(s->current.request_to == ORIGIN_SERVER);
-  ink_assert(s->current.server == &s->server_info);
-  update_dns_info(&s->dns_info, &s->current);
-  s->current.attempts++;
-  TxnDebug("http_trans", "attempts now: %d, max: %d", s->current.attempts, max_retries);
-  TRANSACT_RETURN(SM_ACTION_ORIGIN_SERVER_RR_MARK_DOWN, ReDNSRoundRobin);
-}
-
 void
 HttpTransact::error_log_connection_failure(State *s, ServerState_t conn_state)
 {
@@ -4022,10 +3931,10 @@ HttpTransact::handle_server_connection_not_open(State *s)
     build_response_from_cache(s, HTTP_WARNING_CODE_REVALIDATION_FAILED);
   } else {
     switch (s->current.request_to) {
-    case PARENT_PROXY:
+    case ResolveInfo::PARENT_PROXY:
       handle_parent_died(s);
       break;
-    case ORIGIN_SERVER:
+    case ResolveInfo::ORIGIN_SERVER:
       handle_server_died(s);
       break;
     default:
@@ -4061,7 +3970,7 @@ HttpTransact::handle_forward_server_connection_open(State *s)
   ink_release_assert(s->current.state == CONNECTION_ALIVE);
 
   HTTPVersion real_version = s->state_machine->get_server_version(s->hdr_info.server_response);
-  if (real_version != s->host_db_info.app.http_data.http_version) {
+  if (real_version != s->dns_info.http_version) {
     // Need to update the hostdb
     s->updated_server_version = real_version;
     TxnDebug("http_trans", "Update hostdb history of server HTTP version 0x%x", s->updated_server_version.get_flat_version());
@@ -5291,22 +5200,22 @@ HttpTransact::get_ka_info_from_host_db(State *s, ConnectionAttributes *server_in
     break;
   }
 
-  if (force_http11 == true || (http11_if_hostdb == true && host_db_info->app.http_data.http_version == HTTP_1_1)) {
+  if (force_http11 == true || (http11_if_hostdb == true && host_db_info->http_version == HTTP_1_1)) {
     server_info->http_version = HTTP_1_1;
     server_info->keep_alive   = HTTP_KEEPALIVE;
-  } else if (host_db_info->app.http_data.http_version == HTTP_1_0) {
+  } else if (host_db_info->http_version == HTTP_1_0) {
     server_info->http_version = HTTP_1_0;
     server_info->keep_alive   = HTTP_KEEPALIVE;
-  } else if (host_db_info->app.http_data.http_version == HTTP_0_9) {
+  } else if (host_db_info->http_version == HTTP_0_9) {
     server_info->http_version = HTTP_0_9;
     server_info->keep_alive   = HTTP_NO_KEEPALIVE;
   } else {
     //////////////////////////////////////////////
     // not set yet for this host. set defaults. //
     //////////////////////////////////////////////
-    server_info->http_version                = HTTP_1_0;
-    server_info->keep_alive                  = HTTP_KEEPALIVE;
-    host_db_info->app.http_data.http_version = HTTP_1_0;
+    server_info->http_version  = HTTP_1_0;
+    server_info->keep_alive    = HTTP_KEEPALIVE;
+    host_db_info->http_version = HTTP_1_0;
   }
 
   /////////////////////////////
@@ -5852,7 +5761,7 @@ HttpTransact::initialize_state_variables_from_request(State *s, HTTPHdr *obsolet
   // the expanded host for cache lookup, and //
   // the host ip for reverse proxy.          //
   /////////////////////////////////////////////
-  s->dns_info.looking_up  = ORIGIN_SERVER;
+  s->dns_info.looking_up  = ResolveInfo::ORIGIN_SERVER;
   s->dns_info.lookup_name = s->server_info.name;
 }
 
@@ -6248,7 +6157,7 @@ HttpTransact::is_response_cacheable(State *s, HTTPHdr *request, HTTPHdr *respons
   // host addresses, do not allow cache.  This may cause DNS cache poisoning
   // of other trafficserver clients. The flag is set in the
   // process_host_db_info method
-  if (!s->dns_info.lookup_validated && s->client_info.is_transparent) {
+  if (!s->dns_info.cta_validated_p && s->client_info.is_transparent) {
     TxnDebug("http_trans", "Lookup not validated.  Possible DNS cache poison.  Don't cache");
     return false;
   }
@@ -6638,7 +6547,7 @@ HttpTransact::will_this_request_self_loop(State *s)
   ////////////////////////////////////////
   // check if we are about to self loop //
   ////////////////////////////////////////
-  if (s->dns_info.lookup_success) {
+  if (s->dns_info.active) {
     TxnDebug("http_transact", "max_proxy_cycles = %d", max_proxy_cycles);
     if (max_proxy_cycles == 0) {
       in_port_t dst_port   = s->hdr_info.client_request.url_get()->port_get(); // going to this port.
@@ -6646,13 +6555,13 @@ HttpTransact::will_this_request_self_loop(State *s)
       // It's a loop if connecting to the same port as it already connected to the proxy and
       // it's a proxy address or the same address it already connected to.
       TxnDebug("http_transact", "dst_port = %d local_port = %d", dst_port, local_port);
-      if (dst_port == local_port && (ats_ip_addr_eq(s->host_db_info.ip(), &Machine::instance()->ip.sa) ||
-                                     ats_ip_addr_eq(s->host_db_info.ip(), s->client_info.dst_addr))) {
+      if (dst_port == local_port && ((s->dns_info.active->data.ip == &Machine::instance()->ip.sa) ||
+                                     (s->dns_info.active->data.ip == s->client_info.dst_addr))) {
         switch (s->dns_info.looking_up) {
-        case ORIGIN_SERVER:
+        case ResolveInfo::ORIGIN_SERVER:
           TxnDebug("http_transact", "host ip and port same as local ip and port - bailing");
           break;
-        case PARENT_PROXY:
+        case ResolveInfo::PARENT_PROXY:
           TxnDebug("http_transact", "parent proxy ip and port same as local ip and port - bailing");
           break;
         default:
@@ -6883,7 +6792,7 @@ HttpTransact::handle_request_keep_alive_headers(State *s, HTTPVersion ver, HTTPH
     case KA_CONNECTION:
       ink_assert(s->current.server->keep_alive != HTTP_NO_KEEPALIVE);
       if (ver == HTTP_1_0) {
-        if (s->current.request_to == PARENT_PROXY && parent_is_proxy(s)) {
+        if (s->current.request_to == ResolveInfo::PARENT_PROXY && parent_is_proxy(s)) {
           heads->value_set(MIME_FIELD_PROXY_CONNECTION, MIME_LEN_PROXY_CONNECTION, "keep-alive", 10);
         } else {
           heads->value_set(MIME_FIELD_CONNECTION, MIME_LEN_CONNECTION, "keep-alive", 10);
@@ -6897,7 +6806,7 @@ HttpTransact::handle_request_keep_alive_headers(State *s, HTTPVersion ver, HTTPH
       if (s->current.server->keep_alive != HTTP_NO_KEEPALIVE || (ver == HTTP_1_1)) {
         /* Had keep-alive */
         s->current.server->keep_alive = HTTP_NO_KEEPALIVE;
-        if (s->current.request_to == PARENT_PROXY && parent_is_proxy(s)) {
+        if (s->current.request_to == ResolveInfo::PARENT_PROXY && parent_is_proxy(s)) {
           heads->value_set(MIME_FIELD_PROXY_CONNECTION, MIME_LEN_PROXY_CONNECTION, "close", 5);
         } else {
           ProxyTransaction *svr = s->state_machine->get_server_txn();
@@ -7809,7 +7718,7 @@ HttpTransact::build_request(State *s, HTTPHdr *base_request, HTTPHdr *outgoing_r
   if (outgoing_request->method_get_wksidx() == HTTP_WKSIDX_CONNECT) {
     // CONNECT method requires a target in the URL, so always force it from the Host header.
     outgoing_request->set_url_target_from_host_field();
-  } else if (s->current.request_to == PARENT_PROXY && parent_is_proxy(s)) {
+  } else if (s->current.request_to == ResolveInfo::PARENT_PROXY && parent_is_proxy(s)) {
     // If we have a parent proxy set the URL target field.
     if (!outgoing_request->is_target_in_url()) {
       TxnDebug("http_trans", "adding target to URL for parent proxy");
@@ -8836,7 +8745,7 @@ HttpTransact::update_size_and_time_stats(State *s, ink_hrtime total_time, ink_hr
   HTTP_SUM_DYN_STAT(http_user_agent_response_document_total_size_stat, user_agent_response_body_size);
 
   // proxy stats
-  if (s->current.request_to == HttpTransact::PARENT_PROXY) {
+  if (s->current.request_to == ResolveInfo::PARENT_PROXY) {
     HTTP_SUM_DYN_STAT(http_parent_proxy_request_total_bytes_stat,
                       origin_server_request_header_size + origin_server_request_body_size);
     HTTP_SUM_DYN_STAT(http_parent_proxy_response_total_bytes_stat,
diff --git a/proxy/http/HttpTransact.h b/proxy/http/HttpTransact.h
index 7e7fc71ea..e9700f831 100644
--- a/proxy/http/HttpTransact.h
+++ b/proxy/http/HttpTransact.h
@@ -282,14 +282,6 @@ public:
     HTTP_TRANSACT_MAGIC_SEPARATOR = 0x12345678
   };
 
-  enum LookingUp_t {
-    ORIGIN_SERVER,
-    UNDEFINED_LOOKUP,
-    PARENT_PROXY,
-    INCOMING_ROUTER,
-    HOST_NONE,
-  };
-
   enum ProxyMode_t {
     UNDEFINED_MODE,
     GENERIC_PROXY,
@@ -574,57 +566,19 @@ public:
   };
 
   typedef struct _CurrentInfo {
-    ProxyMode_t mode                           = UNDEFINED_MODE;
-    LookingUp_t request_to                     = UNDEFINED_LOOKUP;
-    ConnectionAttributes *server               = nullptr;
-    ink_time_t now                             = 0;
-    ServerState_t state                        = STATE_UNDEFINED;
-    unsigned attempts                          = 0;
-    unsigned simple_retry_attempts             = 0;
-    unsigned unavailable_server_retry_attempts = 0;
-    ParentRetry_t retry_type                   = PARENT_RETRY_NONE;
+    ProxyMode_t mode                             = UNDEFINED_MODE;
+    ResolveInfo::UpstreamResolveStyle request_to = ResolveInfo::UNDEFINED_LOOKUP;
+    ConnectionAttributes *server                 = nullptr;
+    ink_time_t now                               = 0;
+    ServerState_t state                          = STATE_UNDEFINED;
+    unsigned attempts                            = 0;
+    unsigned simple_retry_attempts               = 0;
+    unsigned unavailable_server_retry_attempts   = 0;
+    ParentRetry_t retry_type                     = PARENT_RETRY_NONE;
 
     _CurrentInfo() {}
   } CurrentInfo;
 
-  typedef struct _DNSLookupInfo {
-    /** Origin server address source selection.
-
-        If config says to use CTA (client target addr) state is
-        OS_ADDR_TRY_CLIENT, otherwise it remains the default. If the
-        connect fails then we switch to a USE. We go to USE_HOSTDB if
-        (1) the HostDB lookup is successful and (2) some address other
-        than the CTA is available to try. Otherwise we keep retrying
-        on the CTA (USE_CLIENT) up to the max retry value.  In essence
-        we try to treat the CTA as if it were another RR value in the
-        HostDB record.
-     */
-    enum class OS_Addr {
-      OS_ADDR_TRY_DEFAULT, ///< Initial state, use what config says.
-      OS_ADDR_TRY_HOSTDB,  ///< Try HostDB data.
-      OS_ADDR_TRY_CLIENT,  ///< Try client target addr.
-      OS_ADDR_USE_HOSTDB,  ///< Force use of HostDB target address.
-      OS_ADDR_USE_CLIENT   ///< Use client target addr, no fallback.
-    };
-
-    OS_Addr os_addr_style = OS_Addr::OS_ADDR_TRY_DEFAULT;
-
-    bool lookup_success         = false;
-    char *lookup_name           = nullptr;
-    char srv_hostname[MAXDNAME] = {0};
-    LookingUp_t looking_up      = UNDEFINED_LOOKUP;
-    bool srv_lookup_success     = false;
-    short srv_port              = 0;
-    HostDBApplicationInfo srv_app;
-
-    /*** Set to true by default.  If use_client_target_address is set
-     * to 1, this value will be set to false if the client address is
-     * not in the DNS pool */
-    bool lookup_validated = true;
-
-    _DNSLookupInfo() {}
-  } DNSLookupInfo;
-
   // Conversion handling for DNS host resolution type.
   static const MgmtConverter HOST_RES_CONV;
 
@@ -672,7 +626,7 @@ public:
 
     HttpConfigParams *http_config_param = nullptr;
     CacheLookupInfo cache_info;
-    DNSLookupInfo dns_info;
+    ResolveInfo dns_info;
     RedirectInfo redirect_info;
     OutboundConnTrack::TxnState outbound_conn_track_state;
     HTTPVersion updated_server_version    = HTTP_INVALID;
@@ -724,8 +678,6 @@ public:
     int orig_scheme          = scheme; // pre-mapped scheme
     int method               = 0;
     int cause_of_death_errno = -UNKNOWN_INTERNAL_ERROR; // in
-    Ptr<HostDBInfo> hostdb_entry;                       // Pointer to the entry we are referencing in hostdb-- to keep our ref
-    HostDBInfo host_db_info;                            // in
 
     ink_time_t client_request_time    = UNDEFINED_TIME; // internal
     ink_time_t request_sent_time      = UNDEFINED_TIME; // internal
@@ -774,7 +726,6 @@ public:
     bool api_server_request_body_set              = false;
     bool api_req_cacheable                        = false;
     bool api_resp_cacheable                       = false;
-    bool api_server_addr_set                      = false;
     UpdateCachedObject_t api_update_cached_object = UPDATE_CACHED_OBJECT_NONE;
     StateMachineAction_t saved_update_next_action = SM_ACTION_UNDEFINED;
     CacheAction_t saved_update_cache_action       = CACHE_DO_UNDEFINED;
@@ -822,6 +773,7 @@ public:
     init()
     {
       parent_params = ParentConfig::acquire();
+      new (&dns_info) decltype(dns_info); // reset to default state.
     }
 
     // Constructor
@@ -848,7 +800,8 @@ public:
       via_string[VIA_DETAIL_SERVER_DESCRIPTOR] = VIA_DETAIL_SERVER_DESCRIPTOR_STRING;
       via_string[MAX_VIA_INDICES]              = '\0';
 
-      memset((void *)&host_db_info, 0, sizeof(host_db_info));
+      //      memset(user_args, 0, sizeof(user_args));
+      //      memset((void *)&host_db_info, 0, sizeof(host_db_info));
     }
 
     void
@@ -878,7 +831,7 @@ public:
       url_map.clear();
       arena.reset();
       unmapped_url.clear();
-      hostdb_entry.clear();
+      dns_info.~ResolveInfo();
       outbound_conn_track_state.clear();
 
       delete[] ranges;
@@ -922,6 +875,7 @@ public:
       if (e != EIO) {
         this->cause_of_death_errno = e;
       }
+      Debug("http", "Setting upstream connection failure %d to %d", e, this->current.server->connect_result);
     }
 
   private:
@@ -953,7 +907,6 @@ public:
 
   static void CallOSDNSLookup(State *s);
   static void OSDNSLookup(State *s);
-  static void ReDNSRoundRobin(State *s);
   static void PPDNSLookup(State *s);
   static void PPDNSLookupAPICall(State *s);
   static void OriginServerRawOpen(State *s);
diff --git a/proxy/http/PreWarmManager.cc b/proxy/http/PreWarmManager.cc
index 930d71ed7..ffddf5d93 100644
--- a/proxy/http/PreWarmManager.cc
+++ b/proxy/http/PreWarmManager.cc
@@ -27,6 +27,7 @@
 #include "HttpConfig.h"
 #include "P_SSLSNI.h"
 
+#include "tscore/ink_time.h"
 #include "tscpp/util/PostScript.h"
 
 #include <algorithm>
@@ -41,8 +42,8 @@ namespace
 {
 using namespace std::literals;
 
-constexpr int DOWN_SERVER_TIMEOUT  = 300;
-constexpr size_t STAT_NAME_BUF_LEN = 1024;
+constexpr ts_seconds DOWN_SERVER_TIMEOUT = 300s;
+constexpr size_t STAT_NAME_BUF_LEN       = 1024;
 
 constexpr std::string_view SRV_TUNNEL_TCP                = "_tunnel._tcp."sv;
 constexpr std::string_view CLIENT_SNI_POLICY_SERVER_NAME = "server_name"sv;
@@ -265,22 +266,29 @@ PreWarmSM::state_init(int event, void *data)
 int
 PreWarmSM::state_dns_lookup(int event, void *data)
 {
-  HostDBInfo *info = static_cast<HostDBInfo *>(data);
+  HostDBRecord *record = static_cast<HostDBRecord *>(data);
 
   switch (event) {
   case EVENT_HOST_DB_LOOKUP: {
     _pending_action = nullptr;
 
-    if (info == nullptr || info->is_failed()) {
+    if (record == nullptr || record->is_failed()) {
       PreWarmSMVDebug("hostdb lookup is failed");
 
       retry();
       return EVENT_DONE;
     }
 
+    HostDBInfo *info = record->select_next_rr(ts_clock::now(), DOWN_SERVER_TIMEOUT);
+
+    if (info == nullptr) {
+      PreWarmSMVDebug("hostdb lookup found no entry");
+      retry();
+      return EVENT_DONE;
+    }
     IpEndpoint addr;
+    addr.assign(info->data.ip);
 
-    ats_ip_copy(addr, info->ip());
     addr.network_order_port() = htons(_dst->port);
 
     if (is_debug_tag_set("v_prewarm_sm")) {
@@ -302,23 +310,19 @@ PreWarmSM::state_dns_lookup(int event, void *data)
     _pending_action = nullptr;
     std::string_view hostname;
 
-    if (info == nullptr || !info->is_srv || !info->round_robin) {
+    if (record == nullptr || !record->is_srv()) {
       // no SRV record, fallback to default lookup
       hostname = _dst->host;
     } else {
-      HostDBRoundRobin *rr = info->rr();
-      HostDBInfo *srv      = nullptr;
-      if (rr) {
-        char srv_hostname[MAXDNAME] = {0};
-
-        ink_hrtime now = Thread::get_hrtime();
-        srv = rr->select_best_srv(srv_hostname, &mutex->thread_holding->generator, ink_hrtime_to_sec(now), DOWN_SERVER_TIMEOUT);
-        hostname = std::string_view(srv_hostname);
-
-        if (srv == nullptr) {
-          // lookup SRV record failed, fallback to default lookup
-          hostname = _dst->host;
-        }
+      char srv_hostname[MAXDNAME] = {0};
+
+      hostname = std::string_view(srv_hostname);
+
+      HostDBInfo *info =
+        record->select_best_srv(srv_hostname, &mutex->thread_holding->generator, ts_clock::now(), DOWN_SERVER_TIMEOUT);
+      if (info == nullptr) {
+        // lookup SRV record failed, fallback to default lookup
+        hostname = _dst->host;
       }
     }
 
@@ -508,7 +512,7 @@ PreWarmSM::is_inactive_timeout_expired(ink_hrtime now)
 }
 
 void
-PreWarmSM::process_hostdb_info(HostDBInfo *r)
+PreWarmSM::process_hostdb_info(HostDBRecord *r)
 {
   ink_release_assert(this->handler == &PreWarmSM::state_dns_lookup);
 
@@ -516,7 +520,7 @@ PreWarmSM::process_hostdb_info(HostDBInfo *r)
 }
 
 void
-PreWarmSM::process_srv_info(HostDBInfo *r)
+PreWarmSM::process_srv_info(HostDBRecord *r)
 {
   ink_release_assert(this->handler == &PreWarmSM::state_dns_lookup);
 
diff --git a/proxy/http/PreWarmManager.h b/proxy/http/PreWarmManager.h
index e7da819c2..ea3506365 100644
--- a/proxy/http/PreWarmManager.h
+++ b/proxy/http/PreWarmManager.h
@@ -192,8 +192,8 @@ public:
   bool is_inactive_timeout_expired(ink_hrtime now);
 
   // HostDB inline completion functions
-  void process_hostdb_info(HostDBInfo *r);
-  void process_srv_info(HostDBInfo *r);
+  void process_hostdb_info(HostDBRecord *r);
+  void process_srv_info(HostDBRecord *r);
 
 private:
   enum class Milestone {
diff --git a/proxy/http/remap/unit-tests/nexthop_test_stubs.cc b/proxy/http/remap/unit-tests/nexthop_test_stubs.cc
index b72183b21..905651261 100644
--- a/proxy/http/remap/unit-tests/nexthop_test_stubs.cc
+++ b/proxy/http/remap/unit-tests/nexthop_test_stubs.cc
@@ -86,7 +86,6 @@ br_destroy(HttpSM &sm)
   }
   delete h->hdr;
   delete h->api_info;
-  ats_free(h->hostname_str);
 }
 
 void
@@ -102,10 +101,7 @@ build_request(int64_t sm_id, HttpSM *sm, sockaddr_in *ip, const char *os_hostnam
   }
   sm->t_state.request_data.hdr = new HTTPHdr();
   sm->t_state.request_data.hdr->create(HTTP_TYPE_REQUEST, myHeap);
-
-  ats_free(sm->t_state.request_data.hostname_str);
-
-  sm->t_state.request_data.hostname_str = ats_strdup(os_hostname);
+  sm->t_state.request_data.hostname_str = sm->t_state.arena.str_store(os_hostname, strlen(os_hostname));
   sm->t_state.request_data.xact_start   = time(nullptr);
   ink_zero(sm->t_state.request_data.src_ip);
   ink_zero(sm->t_state.request_data.dest_ip);
diff --git a/src/traffic_server/InkAPI.cc b/src/traffic_server/InkAPI.cc
index 8743e1701..1b19095e7 100644
--- a/src/traffic_server/InkAPI.cc
+++ b/src/traffic_server/InkAPI.cc
@@ -5893,12 +5893,11 @@ TSHttpTxnServerAddrSet(TSHttpTxn txnp, struct sockaddr const *addr)
   sdk_assert(sdk_sanity_check_txn(txnp) == TS_SUCCESS);
 
   HttpSM *sm = reinterpret_cast<HttpSM *>(txnp);
-  if (ats_ip_copy(&sm->t_state.server_info.dst_addr.sa, addr)) {
-    sm->t_state.api_server_addr_set = true;
+  if (sm->t_state.dns_info.set_upstream_address(addr)) {
+    sm->t_state.dns_info.os_addr_style = ResolveInfo::OS_Addr::USE_API;
     return TS_SUCCESS;
-  } else {
-    return TS_ERROR;
   }
+  return TS_ERROR;
 }
 
 void
@@ -7595,6 +7594,30 @@ TSNetAcceptNamedProtocol(TSCont contp, const char *protocol)
 }
 
 /* DNS Lookups */
+/// Context structure for the lookup callback to the plugin.
+struct TSResolveInfo {
+  IpEndpoint addr;                ///< Lookup result.
+  HostDBRecord *record = nullptr; ///< Record for the FQDN.
+};
+
+int
+TSHostLookupTrampoline(TSCont contp, TSEvent ev, void *data)
+{
+  auto c = reinterpret_cast<INKContInternal *>(contp);
+  // Set up the local context.
+  TSResolveInfo ri;
+  ri.record = static_cast<HostDBRecord *>(data);
+  if (ri.record) {
+    ri.record->rr_info()[0].data.ip.toSockAddr(ri.addr);
+  }
+  auto *target = reinterpret_cast<INKContInternal *>(c->mdata);
+  // Deliver the message.
+  target->handleEvent(ev, &ri);
+  // Cleanup.
+  c->destroy();
+  return TS_SUCCESS;
+};
+
 TSAction
 TSHostLookup(TSCont contp, const char *hostname, size_t namelen)
 {
@@ -7604,16 +7627,23 @@ TSHostLookup(TSCont contp, const char *hostname, size_t namelen)
 
   FORCE_PLUGIN_SCOPED_MUTEX(contp);
 
-  INKContInternal *i = (INKContInternal *)contp;
-  return (TSAction)hostDBProcessor.getbyname_re(i, hostname, namelen);
+  // There is no place to store the actual sockaddr to which a pointer should be returned.
+  // therefore an intermediate continuation is created to intercept the reply from HostDB.
+  // Its handler can create the required sockaddr context on the stack and then forward
+  // the event to the plugin continuation. The sockaddr cannot be placed in the HostDB
+  // record because that is a shared object.
+  auto bouncer = INKContAllocator.alloc();
+  bouncer->init(&TSHostLookupTrampoline, reinterpret_cast<TSMutex>(reinterpret_cast<INKContInternal *>(contp)->mutex.get()));
+  bouncer->mdata = contp;
+  return (TSAction)hostDBProcessor.getbyname_re(bouncer, hostname, namelen);
 }
 
 sockaddr const *
 TSHostLookupResultAddrGet(TSHostLookupResult lookup_result)
 {
   sdk_assert(sdk_sanity_check_hostlookup_structure(lookup_result) == TS_SUCCESS);
-  HostDBInfo *di = reinterpret_cast<HostDBInfo *>(lookup_result);
-  return di->ip();
+  auto ri{reinterpret_cast<TSResolveInfo *>(lookup_result)};
+  return ri->addr.isValid() ? &ri->addr.sa : nullptr;
 }
 
 /*
@@ -8662,6 +8692,9 @@ _memberp_to_generic(MgmtFloat *ptr, MgmtConverter const *&conv) -> typename std:
 static void *
 _conf_to_memberp(TSOverridableConfigKey conf, OverridableHttpConfigParams *overridableHttpConfig, MgmtConverter const *&conv)
 {
+  // External converters.
+  extern MgmtConverter const &HostDBDownServerCacheTimeConv;
+
   void *ret = nullptr;
   conv      = nullptr;
 
@@ -8820,7 +8853,8 @@ _conf_to_memberp(TSOverridableConfigKey conf, OverridableHttpConfigParams *overr
     ret = _memberp_to_generic(&overridableHttpConfig->post_connect_attempts_timeout, conv);
     break;
   case TS_CONFIG_HTTP_DOWN_SERVER_CACHE_TIME:
-    ret = _memberp_to_generic(&overridableHttpConfig->down_server_timeout, conv);
+    conv = &HostDBDownServerCacheTimeConv;
+    ret  = &overridableHttpConfig->down_server_timeout;
     break;
   case TS_CONFIG_HTTP_DOWN_SERVER_ABORT_THRESHOLD:
     ret = _memberp_to_generic(&overridableHttpConfig->client_abort_threshold, conv);
diff --git a/src/traffic_server/InkAPITest.cc b/src/traffic_server/InkAPITest.cc
index f6b070082..96526d9fa 100644
--- a/src/traffic_server/InkAPITest.cc
+++ b/src/traffic_server/InkAPITest.cc
@@ -8512,14 +8512,14 @@ EXCLUSIVE_REGRESSION_TEST(SDK_API_TSHttpConnectIntercept)(RegressionTest *test,
   /* ip and log do not matter as it is used for logging only */
   sockaddr_in addr;
   ats_ip4_set(&addr, 1, 1);
-  data->vc = TSHttpConnect(ats_ip_sa_cast(&addr));
+  data->vc = TSHttpConnectWithPluginId(ats_ip_sa_cast(&addr), "TSHttpConnectIntercept", 1);
   if (TSVConnClosedGet(data->vc)) {
     SDK_RPRINT(data->test, "TSHttpConnect", "TestCase 1", TC_FAIL, "Connect reported as closed immediately after open");
   }
   synclient_txn_send_request_to_vc(data->browser, data->request, data->vc);
 
   /* Wait until transaction is done */
-  TSContScheduleOnPool(cont_test, 25, TS_THREAD_POOL_NET);
+  TSContScheduleOnPool(cont_test, 100, TS_THREAD_POOL_NET);
 
   return;
 }
@@ -8558,12 +8558,12 @@ EXCLUSIVE_REGRESSION_TEST(SDK_API_TSHttpConnectServerIntercept)(RegressionTest *
   /* ip and log do not matter as it is used for logging only */
   sockaddr_in addr;
   ats_ip4_set(&addr, 2, 2);
-  data->vc = TSHttpConnect(ats_ip_sa_cast(&addr));
+  data->vc = TSHttpConnectWithPluginId(ats_ip_sa_cast(&addr), "TSHttpConnectServerIntercept", 1);
 
   synclient_txn_send_request_to_vc(data->browser, data->request, data->vc);
 
   /* Wait until transaction is done */
-  TSContScheduleOnPool(cont_test, 25, TS_THREAD_POOL_NET);
+  TSContScheduleOnPool(cont_test, 100, TS_THREAD_POOL_NET);
 
   return;
 }
diff --git a/src/tscore/unit_tests/test_BufferWriterFormat.cc b/src/tscore/unit_tests/test_BufferWriterFormat.cc
index 66004f97d..76d97080e 100644
--- a/src/tscore/unit_tests/test_BufferWriterFormat.cc
+++ b/src/tscore/unit_tests/test_BufferWriterFormat.cc
@@ -284,9 +284,11 @@ TEST_CASE("bwstring", "[bwprint][bwstring]")
 
   ts::bwprint(s, fmt, 99999, text);
   REQUIRE(s == "99999 -- e99a18c428cb38d5f260853678922e03");
+  REQUIRE(strlen(s.c_str()) == text.size() + 9);
 
   ts::bwprint(s, "{} .. |{:,20}|", 32767, text);
   REQUIRE(s == "32767 .. |e99a18c428cb38d5f260|");
+  REQUIRE(strlen(s.c_str()) == 31);
 
   ts::LocalBufferWriter<128> bw;
   char buff[128];
diff --git a/tests/gold_tests/next_hop/strategies_ch2/strategies_ch2.test.py b/tests/gold_tests/next_hop/strategies_ch2/strategies_ch2.test.py
index 9d7f97652..0ad0c4986 100644
--- a/tests/gold_tests/next_hop/strategies_ch2/strategies_ch2.test.py
+++ b/tests/gold_tests/next_hop/strategies_ch2/strategies_ch2.test.py
@@ -74,6 +74,7 @@ ts.Disk.records_config.update({
     'proxy.config.http.uncacheable_requests_bypass_parent': 0,
     'proxy.config.http.no_dns_just_forward_to_parent': 1,
     'proxy.config.http.parent_proxy.mark_down_hostdb': 0,
+    'proxy.config.http.down_server.cache_time': 1,
     'proxy.config.http.parent_proxy.self_detect': 0,
 })
 
@@ -90,6 +91,7 @@ for i in range(num_nh):
     # The health check URL does not seem to be used currently.
     # s.AddLine(f"          health_check_url: http://next_hop{i}:{ts_nh[i].Variables.port}")
     s.AddLine(f"      weight: 1.0")
+
 s.AddLines([
     "strategies:",
     "  - strategy: the-strategy",
diff --git a/tests/gold_tests/proxy_protocol/proxy_serve_stale.test.py b/tests/gold_tests/proxy_protocol/proxy_serve_stale.test.py
index e012d394f..e799884c6 100644
--- a/tests/gold_tests/proxy_protocol/proxy_serve_stale.test.py
+++ b/tests/gold_tests/proxy_protocol/proxy_serve_stale.test.py
@@ -47,7 +47,7 @@ class ProxyServeStaleTest:
             'proxy.config.http.cache.max_stale_age': 10,
             'proxy.config.http.parent_proxy.self_detect': 0,
             'proxy.config.diags.debug.enabled': 1,
-            'proxy.config.diags.debug.tags': 'http|dns|parent_proxy',
+            'proxy.config.diags.debug.tags': 'cache|http|dns|hostdb|parent_proxy',
         })
         self.ts_child.Disk.parent_config.AddLine(
             f'dest_domain=. parent="{self.ts_parent_hostname}" round_robin=consistent_hash go_direct=false'
diff --git a/tests/gold_tests/tls/tls_verify_override_base.test.py b/tests/gold_tests/tls/tls_verify_override_base.test.py
index 0495ec54b..00027f230 100644
--- a/tests/gold_tests/tls/tls_verify_override_base.test.py
+++ b/tests/gold_tests/tls/tls_verify_override_base.test.py
@@ -113,7 +113,9 @@ ts.Disk.records_config.update({
     'proxy.config.url_remap.pristine_host_hdr': 1,
     'proxy.config.exec_thread.autoconfig.scale': 1.0,
     'proxy.config.dns.nameservers': '127.0.0.1:{0}'.format(dns.Variables.Port),
-    'proxy.config.dns.resolv_conf': 'NULL'
+    'proxy.config.dns.resolv_conf': 'NULL',
+    'proxy.config.ssl.client.verify.server.policy': 'PERMISSIVE',
+    'proxy.config.http.connect.dead.policy': 1,  # Don't count TLS failures for dead upstream.
 })
 
 dns.addRecords(records={"foo.com.": ["127.0.0.1"]})