<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Frank DENIS random thoughts.</title>
  <link href="http://00f.net/atom.xml" rel="self"/>
  <link href="http://00f.net/"/>
  <updated>2012-01-14T09:38:08+01:00</updated>
  <id>http://00f.net</id>
  
  <author>
    <name>Frank Denis (Jedi/Sector One)</name>
  </author>
  
  
  <entry>
    <title>DNS records and TTL - how long does a second actually last?</title>
    <link href="http://00f.net/2011/11/17/how-long-does-a-dns-ttl-last/"/>
   <updated>2011-11-17T00:00:00+01:00</updated>
   <id>http://00f.net/2011/11/17/how-long-does-a-dns-ttl-last</id>
   <content type="html">&lt;p&gt;In a DNS zone, every record carries its own time-to-live, so that it can
be cached, yet still changed if necessary.&lt;/p&gt;

&lt;p&gt;This information is originally served by authoritative servers for the
related zone. The TTL is represented as an integer number of seconds.&lt;/p&gt;

&lt;p&gt;At first sight, the mechanism looks straightforward: if the
&lt;code&gt;www.example.com&lt;/code&gt; record has a TTL of 30, it's only valid up to 30
second, and caches must fetch it again if requested after this delay.&lt;/p&gt;

&lt;h1&gt;Chained caches&lt;/h1&gt;

&lt;p&gt;DNS caches can be chained: instead of directly querying authoritative
servers, a cache can forward queries to a another server, and cache
the result.&lt;/p&gt;

&lt;p&gt;Having 3 or 4 chained caches is actually very common. Web browsers,
operating systems and routers can cache and forward DNS a query.
Eventually, this query will be sent to an upstream cache, like the ISP
cache or a third-party service like &lt;em&gt;OpenDNS&lt;/em&gt;. And these caches can also
actually hide multiple chained caches.&lt;/p&gt;

&lt;p&gt;In order to respect the original TTL, caches are modifying records as
they forward them to clients. The response to a query that has been
sitting in a cache for 10 second will be served with a TTL reduced by
10 second. That way, if the original TTL was 30 second, the whole
chain is guaranteed to consider this record as expired as the same
time: the original meaning of the TTL is retained no matter how many
resolvers there are in the way.&lt;/p&gt;

&lt;p&gt;Well, not exactly. A TTL is just a time interval, not an absolute
date. Unlike a HTTP response, a DNS response doesn't contain any
timestamp. Thus, requests processing and network latency are causing
caches to keep a record longer than they actually should in order to
respect the initial TTL.&lt;/p&gt;

&lt;p&gt;A TTL being an integer value makes things even worse: a chain of &lt;strong&gt;N&lt;/strong&gt; caches
can introduce a &lt;strong&gt;N&lt;/strong&gt; second bias.&lt;/p&gt;

&lt;p&gt;In practice, this is rarely an issue: TTLs as served by authoritative
servers are considered indicative, and not as something to depend on
when accurate timing is required.&lt;/p&gt;

&lt;h1&gt;The decay of a TTL, as seen by different pieces of software&lt;/h1&gt;

&lt;p&gt;How does the TTL of a record served by a DNS cache decays over time?&lt;/p&gt;

&lt;p&gt;Surprisingly, different implementations exhibit different behaviors.&lt;/p&gt;

&lt;p&gt;For a record initially served with a TTL equal to &lt;strong&gt;N&lt;/strong&gt; by authoritative
servers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Google DNS&lt;/em&gt; serves it with a TTL in the interval &lt;strong&gt;[0, N-1]&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;dnscache&lt;/em&gt; is serving it with a TTL in the interval &lt;strong&gt;[0, N]&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Unbound&lt;/em&gt; serves it with a TTL in the interval &lt;strong&gt;[0, N]&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Bind&lt;/em&gt; serves it with a TTL in the interval &lt;strong&gt;[1, N]&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;PowerDNS Recursor&lt;/em&gt; always serves it with a TTL of &lt;strong&gt;N&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;Hold on... Does it mean that how frequently an entry will actually be
refreshed depends on what software resolvers are running?&lt;/p&gt;

&lt;p&gt;Sadly, yes. Given a record with a TTL equal to &lt;strong&gt;N&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Bind&lt;/em&gt; and &lt;em&gt;PowerDNS Recursor&lt;/em&gt; refresh it every &lt;strong&gt;N&lt;/strong&gt; second if necessary&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Unbound&lt;/em&gt; and &lt;em&gt;dnscache&lt;/em&gt; are only refreshing it every &lt;strong&gt;N + 1&lt;/strong&gt; seconds at best.&lt;/li&gt;
&lt;/ul&gt;


&lt;h1&gt;TTL Zero&lt;/h1&gt;

&lt;p&gt;Although &lt;a href=&quot;http://mark.lindsey.name/2009/03/never-use-dns-ttl-of-zero-0.html&quot;&gt;a TTL of zero can cause interoperability issues&lt;/a&gt;,
most DNS caches are considering records with a TTL of zero as records
that should not be cached.&lt;/p&gt;

&lt;p&gt;This perfectly makes sense when the TTL of zero is the original TTL, as served
by authoritative servers.&lt;/p&gt;

&lt;p&gt;However, when a cache artificially changes the TTL to zero, it changes a record
that had been designed for being cached to an uncachable record that
contaminates the rest of the chain.&lt;/p&gt;

&lt;h1&gt;dnscache and a 1 second record&lt;/h1&gt;

&lt;p&gt;To illustrate this, a Linux box has been setup with a local DNS cache.
&lt;em&gt;Unbound&lt;/em&gt; has been chosen, but the last component of the chain actually
makes little difference. Even web browsers caches have a very similar
behavior.&lt;/p&gt;

&lt;p&gt;Queries are forwarded to an upstream cache on the same LAN, running
&lt;em&gt;dnscache&lt;/em&gt;, and outgoing queries are recorded with ngrep. The same
query, whose response has a TTL of &lt;strong&gt;1&lt;/strong&gt;, is made at a 10 queries per
second rate.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;X&lt;/strong&gt; axis represents outgoing queries, whereas the &lt;strong&gt;Y&lt;/strong&gt; axis is the time
elapsed since the previous query.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/posts/ttl/unbound-djbdns-one.jpg&quot; alt=&quot;One sec TTL with an upstream server running dnscache&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Even though a TTL of &lt;strong&gt;1&lt;/strong&gt; is served by authoritative servers for this
record, a large amount of responses are cached and served for &lt;strong&gt;2&lt;/strong&gt;
second. This is due to the local &lt;em&gt;Unbound&lt;/em&gt; resolver.&lt;/p&gt;

&lt;p&gt;However, there is also quite a lot of responses that haven't been cached at
all. It happens when &lt;em&gt;dnscache&lt;/em&gt; serves a response with a TTL of &lt;strong&gt;0&lt;/strong&gt;. Since
it happens one third of the time, this is suboptimal and not on par
with the intent of the authoritate record, which is served with
non-zero TTL.&lt;/p&gt;

&lt;p&gt;And although &lt;em&gt;dnscache&lt;/em&gt; and &lt;em&gt;Unbound&lt;/em&gt; are handling TTLs the same way, we
can't expect their caches to be perfectly synchronized. When the local
cache considers a record as expired and issues an outgoing query, the
upstream server can consider it as not expired yet, just in the middle
of the last second. What we get is a constant race between caches,
causing jitter and outgoing queries sent after 1 second.&lt;/p&gt;

&lt;h1&gt;How different implementations can affect the number of outgoing queries&lt;/h1&gt;

&lt;p&gt;The following experiment has been made by observing outgoing queries
when sending &lt;strong&gt;10,000 queries&lt;/strong&gt; for a record with a TTL of &lt;strong&gt;2&lt;/strong&gt;, at an average
10 qps rate, to a local cache, using &lt;em&gt;Google&lt;/em&gt;, &lt;em&gt;OpenDNS&lt;/em&gt; and
&lt;em&gt;Level 3&lt;/em&gt; as upstream resolvers.&lt;/p&gt;

&lt;p&gt;Here is the number of outgoing queries that were required to complete
the &lt;strong&gt;10,000 local queries&lt;/strong&gt; using different services:&lt;/p&gt;

&lt;table&gt;
  &lt;tr scope=column&gt;
    &lt;th&gt;Service&lt;/th&gt;
    &lt;th&gt;Outgoing queries&lt;/th&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;Google DNS&lt;/td&gt;
    &lt;td&gt;1383&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;OpenDNS&lt;/td&gt;
    &lt;td&gt;632&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;Level 3&lt;/td&gt;
    &lt;td&gt;388&lt;/td&gt;
  &lt;/tr&gt;
&lt;/table&gt;


&lt;p&gt;&lt;img src=&quot;/img/posts/ttl/unbound-remote.jpg&quot; alt=&quot;Same queries, same local resolver, but using different upstream caches: Google DNS, _OpenDNS_ and Level 3&quot; /&gt;&lt;/p&gt;

&lt;p&gt;When using &lt;em&gt;Google DNS&lt;/em&gt; (blue dots), queries are effectively never cached
more than &lt;strong&gt;2 second&lt;/strong&gt;, even locally. This is due to the max TTL returned
by &lt;em&gt;Google DNS&lt;/em&gt; being the initial TTL &lt;strong&gt;minus one&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When &lt;em&gt;Google DNS&lt;/em&gt; returns a TTL of &lt;strong&gt;zero&lt;/strong&gt;, we observe the same jitter and
the same slew of queries that couldn't be served from the local cache.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;OpenDNS&lt;/em&gt; (orange dots) has the same behavior as &lt;em&gt;dnscache&lt;/em&gt; and &lt;em&gt;Unbound&lt;/em&gt;.,
with TTLs in the &lt;strong&gt;[0, N]&lt;/strong&gt; interval.
Our initial TTL with a value of &lt;strong&gt;2&lt;/strong&gt; actually causes &lt;strong&gt;3, 2, and 1 second
delays&lt;/strong&gt; between request, plus a sensitive amount of consecutive
outgoing queries due a null TTL.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Level 3&lt;/em&gt; (green dots) is running &lt;em&gt;Bind&lt;/em&gt;, which returns a TTL in the &lt;strong&gt;[1, N]&lt;/strong&gt;
interval. &lt;em&gt;Bind&lt;/em&gt; caches records &lt;strong&gt;one second less&lt;/strong&gt; than &lt;em&gt;dnscache&lt;/em&gt; and &lt;em&gt;Unbound&lt;/em&gt;.
Because our local resolver is &lt;em&gt;Unbound&lt;/em&gt;, queries received with
a TTL of &lt;strong&gt;N&lt;/strong&gt; are actually cached &lt;strong&gt;N + 1&lt;/strong&gt; second.
But as expected, the frequency of required outgoing queries very rarely
exceeds &lt;strong&gt;3 second&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;What the correct behavior is, is out of scope of this article. All
major implementations are probably correct.&lt;/p&gt;

&lt;p&gt;But from a user perspective, with only &lt;strong&gt;2&lt;/strong&gt; caches in the chain, and for
a given record, the same set of queries can require &lt;strong&gt;up to 3.5 more
outgoing queries&lt;/strong&gt; to get resolved, depending on what software the
remote cache is running.&lt;/p&gt;

&lt;p&gt;With CDNs and popular web sites having records with a very low TTL
(Facebook has a 30 second TTL, Skyrock has a 10 second TTL), the way a
cache handles TTLs can have a sensible impact on performance. That
said, some resolvers can be configured to pre-fetch records before
they expire, effectively mitigating this problem.&lt;/p&gt;

&lt;h1&gt;The OSX cache&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;MacOS X&lt;/em&gt; provides a system-wide name cache, which is enabled by default.&lt;/p&gt;

&lt;p&gt;Its behavior is quite surprising, though. It seems to enforce a
&lt;strong&gt;minimum TTL of 12.5 seconds&lt;/strong&gt;, while still requiring some outgoing
queries delayed by the initial TTL value, and some consecutive ones.
Resolving the &lt;strong&gt;10,000 queries&lt;/strong&gt; from the previous test took only an average
of &lt;strong&gt;232 outgoing queries&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Using &lt;em&gt;Google DNS&lt;/em&gt;, &lt;em&gt;OpenDNS&lt;/em&gt; and &lt;em&gt;Level 3&lt;/em&gt; as a remote resolver
produce the same result, with the exception on &lt;em&gt;Level 3&lt;/em&gt; (&lt;em&gt;Bind&lt;/em&gt;)
avoiding frequent (less than 1 sec) consecutive queries during the
time other return a TTL of zero.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/posts/ttl/osx.jpg&quot; alt=&quot;OSX built-in resolver&quot; /&gt;&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Programmatically changing network configuration on OSX</title>
    <link href="http://00f.net/2011/08/14/programmatically-changing-network-configuration-on-osx/"/>
   <updated>2011-08-14T00:00:00+02:00</updated>
   <id>http://00f.net/2011/08/14/programmatically-changing-network-configuration-on-osx</id>
   <content type="html">&lt;p&gt;Programmatically changing DNS resolvers, IP addresses and proxies on
OSX isn't rocket science but finding documentation on that is like
looking for a needle in a haystack.&lt;/p&gt;

&lt;p&gt;Changing the DNS configuration could have been as simple as updating
&lt;code&gt;/etc/resolv.conf&lt;/code&gt;, as OSX does provide an &lt;code&gt;/etc/resolv.conf&lt;/code&gt; file. But
unlike every other Unix variant out there, OSX doesn't actually read
this file, as it is only there for compatibility with some legacy
tools.&lt;/p&gt;

&lt;h1&gt;Meet configd&lt;/h1&gt;

&lt;blockquote&gt;&lt;p&gt;&quot;The configd daemon is responsible for many configuration aspects of the
local system. configd maintains data reflecting the desired and current
state of the system, provides notifications to applications when this
data changes, and hosts a number of configuration agents in the form of
loadable bundles.&quot;&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;Think about it as a centralized key/value registry. Applications can
watch for changes, add new key/value pairs and modify existing
entries.&lt;/p&gt;

&lt;p&gt;The scutil(8) command provides a command-line interface to this registry:&lt;/p&gt;

&lt;pre&gt;
$ scutil
&amp;gt; list
  subKey [0] = Plugin:IPConfiguration
  subKey [1] = Plugin:InterfaceNamer
  subKey [2] = Setup:
  subKey [3] = Setup:/
  subKey [4] = Setup:/Network/BackToMyMac
  subKey [5] = Setup:/Network/Global/IPv4
  subKey [6] = Setup:/Network/HostNames
  subKey [7] = Setup:/Network/Interface/en0/AirPort
  subKey [8] = Setup:/Network/Service/0CE7A64C-9174-41AC-B634-41F4B6B5FE02
  subKey [9] = Setup:/Network/Service/0CE7A64C-9174-41AC-B634-41F4B6B5FE02/IPv4
  subKey [10] = Setup:/Network/Service/0CE7A64C-9174-41AC-B634-41F4B6B5FE02/IPv6
  subKey [11] = Setup:/Network/Service/0CE7A64C-9174-41AC-B634-41F4B6B5FE02/Interface
  subKey [12] = Setup:/Network/Service/0CE7A64C-9174-41AC-B634-41F4B6B5FE02/Proxies
...  
  subKey [32] = Setup:/Network/Service/527FC752-8B1D-4111-A205-4E81F757704E
  subKey [33] = Setup:/Network/Service/527FC752-8B1D-4111-A205-4E81F757704E/IPv4
  subKey [34] = Setup:/Network/Service/527FC752-8B1D-4111-A205-4E81F757704E/IPv6
  subKey [35] = Setup:/Network/Service/527FC752-8B1D-4111-A205-4E81F757704E/Interface
  subKey [36] = Setup:/Network/Service/527FC752-8B1D-4111-A205-4E81F757704E/Proxies
...
  subKey [58] = Setup:/System
  subKey [59] = State:/IOKit/LowBatteryWarning
  subKey [60] = State:/IOKit/Power/CPUPower
  subKey [61] = State:/IOKit/PowerAdapter
  subKey [62] = State:/IOKit/PowerManagement/Assertions
  subKey [63] = State:/IOKit/PowerManagement/CurrentSettings
  subKey [64] = State:/IOKit/PowerManagement/SystemLoad
  subKey [65] = State:/IOKit/PowerManagement/SystemLoad/Detailed
  subKey [66] = State:/IOKit/PowerSources/InternalBattery-0
  subKey [67] = State:/IOKit/SystemPowerCapabilities
  subKey [68] = State:/Network/BackToMyMac
  subKey [69] = State:/Network/Connectivity
  subKey [70] = State:/Network/Global/DNS
  subKey [71] = State:/Network/Global/IPv4
  subKey [72] = State:/Network/Global/Proxies
  subKey [73] = State:/Network/Interface
  subKey [74] = State:/Network/Interface/en0/AirPort
  subKey [75] = State:/Network/Interface/en0/CaptiveNetwork
  subKey [76] = State:/Network/Interface/en0/IPv4
  subKey [77] = State:/Network/Interface/en0/IPv6
  subKey [78] = State:/Network/Interface/en0/Link
  subKey [79] = State:/Network/Interface/lo0/IPv4
  subKey [80] = State:/Network/Interface/lo0/IPv6
  subKey [81] = State:/Network/Interface/p2p0/Link
  subKey [82] = State:/Network/Interface/utun0/IPv6
  subKey [83] = State:/Network/MulticastDNS
  subKey [84] = State:/Network/NetBIOS
  subKey [85] = State:/Network/PrivateDNS
  subKey [86] = State:/Network/Service/527FC752-8B1D-4111-A205-4E81F757704E/DHCP
  subKey [87] = State:/Network/Service/527FC752-8B1D-4111-A205-4E81F757704E/DNS
  subKey [88] = State:/Network/Service/527FC752-8B1D-4111-A205-4E81F757704E/IPv4
  subKey [89] = State:/Users/ConsoleUser
  subKey [90] = com.apple.DirectoryService.NotifyTypeStandard:DirectoryNodeAdded
  subKey [91] = com.apple.network.identification
  subKey [92] = com.apple.opendirectoryd.node:/Search
  
&amp;gt; get State:/Network/Global/DNS

&amp;gt; d.show
&amp;lt;dictionary&amp;gt; {
  ServerAddresses : &amp;lt;array&amp;gt; {
    0 : 208.67.220.220
    1 : 208.67.222.222
  }
}
&lt;/pre&gt;


&lt;p&gt;As we can see, the registry holds most of the network settings,
including DNS settings. The &lt;code&gt;Setup:/*&lt;/code&gt; entries contain every available
network configuration (as configured in the preferences pane), whereas
&lt;code&gt;State:/*&lt;/code&gt; entries include only what's currently active.&lt;/p&gt;

&lt;p&gt;There are global DNS settings and configuration-specific DNS setings.&lt;/p&gt;

&lt;h1&gt;Programmatically accessing the registry&lt;/h1&gt;

&lt;p&gt;The overlooked &lt;code&gt;DynamicStore&lt;/code&gt; API, which is part of the SystemConfiguration
framework, let us access the &lt;code&gt;configd&lt;/code&gt; registry.&lt;/p&gt;

&lt;p&gt;The following code snippet opens the registry, looks for active
DNS-related entries, and updates them with a new property list
containing OpenDNS resolvers.&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;c&quot;&gt;&lt;span class=&quot;cp&quot;&gt;#include &amp;lt;SystemConfiguration/SystemConfiguration.h&amp;gt;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;setDNS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CFStringRef&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resolvers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CFIndex&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resolvers_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;SCDynamicStoreRef&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ds&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SCDynamicStoreCreate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CFSTR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;setDNS&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    
    &lt;span class=&quot;n&quot;&gt;CFArrayRef&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;array&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CFArrayCreate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resolvers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;resolvers_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kCFTypeArrayCallBacks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    
    &lt;span class=&quot;n&quot;&gt;CFDictionaryRef&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dict&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CFDictionaryCreate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CFStringRef&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[])&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CFSTR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;ServerAddresses&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kCFTypeDictionaryKeyCallBacks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kCFTypeDictionaryValueCallBacks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;    
    
    &lt;span class=&quot;n&quot;&gt;CFArrayRef&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;list&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SCDynamicStoreCopyKeyList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;CFSTR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;State:/Network/(Service/.+|Global)/DNS&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
    
    &lt;span class=&quot;n&quot;&gt;CFIndex&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;j&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CFArrayGetCount&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;j&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;FALSE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TRUE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SCDynamicStoreSetValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CFArrayGetValueAtIndex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;argc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;argv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[])&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;CFStringRef&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resolvers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;CFSTR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;208.67.220.220&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;CFSTR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;208.67.222.222&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;setDNS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resolvers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CFIndex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;sizeof&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resolvers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;sizeof&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resolvers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]));&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;


&lt;p&gt;Of course, changing the registry requires root privileges, but that's
all it takes.&lt;/p&gt;

&lt;p&gt;As new entries can be seamlessly created, you can use the registry for
centralizing configuration in your own apps, or just to save previous
entries before altering them.&lt;/p&gt;

&lt;h1&gt;Changing network services&lt;/h1&gt;

&lt;p&gt;The technique describe above works fine. But the new settings aren't
persistent. More often than once, this actually comes in handy, in
order to apply temporary settings and to rest assured that once the
user reboots his machine, network settings will always be back to a
sane state.&lt;/p&gt;

&lt;p&gt;Tweaking the system configuration database is not recomended by Apple
because some daemons may not be aware of a configuration change.
In addition, you may want to permanently apply the new settings.&lt;/p&gt;

&lt;p&gt;Another way to tackle this problem is to actually alter the network
services.&lt;/p&gt;

&lt;p&gt;Under OSX, a network service is a configuration for a network interface.
To apply a change globally, we need to step over all network services,
check whether DNS (or whatever we want to change) is a support
protocol, and make the related changes.&lt;/p&gt;

&lt;p&gt;This achieves the same thing as what one can do from the Network
preferences pane.&lt;/p&gt;

&lt;p&gt;Almost.&lt;/p&gt;

&lt;p&gt;After changing settings this way, you will notice that ths Network
preferences pane properly reflects them. However, some apps won't have
caught up with the changes. For example, the &lt;code&gt;/etc/resolv.conf&lt;/code&gt; isn't
automatically updated.&lt;/p&gt;

&lt;p&gt;The trick here is to open the system configuration registry as
described above, and then to close it without having done any actual
change. Just &quot;touching&quot; the registry will trigger observers like
&lt;code&gt;configd&lt;/code&gt; so that they can do their own magic.&lt;/p&gt;

&lt;p&gt;An empty DNS servers list or an empty IP/network address means that
DHCP will be used in order to retrieve this information.
But this doesn't happen automatically as the new settings are applied.
Explicitly renewing the DHCP lease is required, by calling
&lt;code&gt;SCNetworkInterfaceForceConfigurationRefresh()&lt;/code&gt; on each interface.&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;objc&quot;&gt;&lt;span class=&quot;k&quot;&gt;@implementation&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;DNSGlobalSettings&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;BOOL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;DNSSupportForNetworkService:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SCNetworkServiceRef&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;networkService&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SCNetworkServiceGetEnabled&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;networkService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;SCNetworkInterfaceRef&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SCNetworkServiceGetInterface&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;networkService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;interface&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;NSArray&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;supportedProtocols&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__bridge_transfer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NSArray&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SCNetworkInterfaceGetSupportedProtocolTypes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;interface&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;supportedProtocols&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;indexOfObject:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__bridge&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NSString&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;kSCNetworkProtocolTypeDNS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NSNotFound&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;YES&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;BOOL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;setResolversTo:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NSArray&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resolvers&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;forNetworkService:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SCNetworkServiceRef&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;networkService&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;DNSSupportForNetworkService:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;networkService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;SCNetworkProtocolRef&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;networkProtocol&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SCNetworkServiceCopyProtocol&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;networkService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;kSCNetworkProtocolTypeDNS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;networkProtocol&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SCNetworkProtocolGetEnabled&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;networkProtocol&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;NSLog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;@&amp;quot;disabled&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;FALSE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resolvers&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;isKindOfClass:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NSArray&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resolvers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;resolvers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resolvers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;NSLog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;@&amp;quot;Setting this service&amp;#39;s resolvers to DHCP&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;NSLog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;@&amp;quot;Setting this service&amp;#39;s resolvers to [%@]&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resolvers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;NSDictionary&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DNSDict&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__bridge&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NSDictionary&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SCNetworkProtocolGetConfiguration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;networkProtocol&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;NSMutableDictionary&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;newDNSDict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;    
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DNSDict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;NSLog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;@&amp;quot;This interface had an existing configuration&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resolvers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;SCNetworkProtocolRef&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;networkProtocol&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SCNetworkServiceCopyProtocol&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;networkService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;kSCNetworkProtocolTypeDNS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;networkProtocol&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SCNetworkProtocolGetEnabled&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;networkProtocol&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;TRUE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;NSLog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;@&amp;quot;Removing protocol from configuration&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;SCNetworkProtocolSetConfiguration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;networkProtocol&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;TRUE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;newDNSDict&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NSMutableDictionary&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;dictionaryWithDictionary:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DNSDict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resolvers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;newDNSDict&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;removeObjectForKey:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__bridge&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NSString&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;kSCPropNetDNSServerAddresses&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;newDNSDict&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;setValue:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resolvers&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;forKey:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__bridge&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NSString&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;kSCPropNetDNSServerAddresses&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;NSLog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;@&amp;quot;This interface had no existing configuration&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resolvers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;            
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;TRUE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;newDNSDict&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NSMutableDictionary&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;dictionaryWithObject:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resolvers&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;forKey:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__bridge&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NSString&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;kSCPropNetDNSServerAddresses&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SCNetworkProtocolSetConfiguration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;networkProtocol&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__bridge&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CFDictionaryRef&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newDNSDict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;BOOL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;touchDynamicStore&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;SCDynamicStoreRef&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ds&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SCDynamicStoreCreate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CFSTR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;myapp&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;CFRelease&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;NSLog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;@&amp;quot;DynamicStore updated&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;TRUE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;BOOL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;forceDHCPUpdate&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;NSLog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;@&amp;quot;Forcing DHCP update&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;NSArray&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;interfaces&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__bridge_transfer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NSArray&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SCNetworkInterfaceCopyAll&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;interface_&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;interfaces&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;SCNetworkInterfaceRef&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__bridge&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SCNetworkInterfaceRef&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;interface_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;SCNetworkInterfaceForceConfigurationRefresh&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;interface&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;TRUE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;BOOL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;applyToNetworkServices:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;BOOL&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;^&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SCNetworkServiceRef&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;networkService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cb&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;andCommit:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;BOOL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;commit&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;SCPreferencesRef&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;preferences&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SCPreferencesCreate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CFSTR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;myapp&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;SCPreferencesLock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;preferences&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;TRUE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;SCNetworkSetRef&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;networkSet&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SCNetworkSetCopyCurrent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;preferences&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; 
    &lt;span class=&quot;n&quot;&gt;NSArray&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;networkSetServices&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__bridge_transfer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NSArray&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SCNetworkSetCopyServices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;networkSet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;BOOL&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;TRUE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;networkService_&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;networkSetServices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;SCNetworkServiceRef&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;networkService&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__bridge&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SCNetworkServiceRef&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;networkService_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;networkService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;SCPreferencesUnlock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;preferences&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;commit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;SCPreferencesCommitChanges&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;preferences&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;SCPreferencesApplyChanges&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;preferences&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;CFRelease&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;networkSet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;    
    &lt;span class=&quot;n&quot;&gt;CFRelease&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;preferences&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;commit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;touchDynamicStore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;BOOL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;setResolvers:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NSArray&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resolvers&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;applyToNetworkServices:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;^&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;BOOL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SCNetworkServiceRef&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;networkService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;setResolversTo:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resolvers&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;forNetworkService:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;networkService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;andCommit:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;YES&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;   
    &lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NSNotificationCenter&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;defaultCenter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;postNotificationName:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;@&amp;quot;CONFIGURATION_CHANGED&amp;quot;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;object:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;userInfo:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;TRUE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;@end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;



</content>
  </entry>
  
  <entry>
    <title>Redis as a recommendation engine</title>
    <link href="http://00f.net/2011/03/10/redis-as-a-recommendation-engine/"/>
   <updated>2011-03-10T00:00:00+01:00</updated>
   <id>http://00f.net/2011/03/10/redis-as-a-recommendation-engine</id>
   <content type="html">&lt;p&gt;&lt;em&gt;&quot;You might be interested in red toilet paper, because you bought blue
and green toilet paper, and people who also bought blue and green
toilet paper tend to also buy red toilet paper&quot;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Recommendation engines are now everywhere, from e-commerce to social networks.&lt;/p&gt;

&lt;p&gt;Graphs databases like Neo4j are probably the best way to tackle the
problem. But as an alternative, let's try building a recommendation
engine on Redis.&lt;/p&gt;

&lt;p&gt;Who bought what or who is following who could be stored in Redis
sorted sets.&lt;/p&gt;

&lt;p&gt;Let's take an example: a user buys A, and also B. We increment the
score of the B item in the sorted set associated to key A. And we
possibly also do it the other way round.&lt;/p&gt;

&lt;p&gt;This is a trivial way to keep a list of what items have been bought,
what are the related items and how many of them there are.&lt;/p&gt;

&lt;p&gt;On a social network, the score associated to a relationship may reflect the
level of trust.&lt;/p&gt;

&lt;p&gt;Let's suppose we have the following relationships:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;B -&amp;gt; C
D -&amp;gt; C
E -&amp;gt; F
A -&amp;gt; B
A -&amp;gt; D
A -&amp;gt; E
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;C is referenced by B and D. B and D are referenced by A.
But C isn't referenced by A yet. This is presumably something to suggest.&lt;/p&gt;

&lt;p&gt;F might also be a good candidate as a suggestion for A. However, since
there's only a single path from A to F (through E), it's probably less
relevant than C.&lt;/p&gt;

&lt;p&gt;In order to limit the number of non-relevant results, we may want to
add a threshold: nodes that have less possible paths than a cutoff
value shouldn't be suggested.&lt;/p&gt;

&lt;p&gt;How to do that with Redis, and only Redis?&lt;/p&gt;

&lt;p&gt;Here's one possible way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Use ZUNIONSTORE in order to build a temporary aggregate of all
related items (scores are summed up), and remove the reference item
from the set (cylic links).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Remove everything from the aggregate that is already directly
related to the reference item.
One way to achieve this on a sorted set is to use ZUNIONSTORE
AGGREGATE MIN with a negative or null weight for the set we want to
delete, followed by ZREMRANGEBYSCORE in order to actually remove
everything from the reference set and everything below the cutoff
score.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sort and optionally trim the new set with ZREVRANGE et voila!&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;Maybe code speaks louder than words, so here we go for some code:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;ruby&quot;&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;redis&amp;quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Recommendation&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;attr_reader&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:db&lt;/span&gt;
  
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;initialize&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@db&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Redis&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;new&lt;/span&gt;  
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;bought&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;suggestions_for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;suggestions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  
  &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Item&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;recommendation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;vi&quot;&gt;@db&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;recommendation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;{rec}:items:&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;tmp_key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;{rec}:_tmp:&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@id&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;and_also_bought&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;linked_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;weight&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;vi&quot;&gt;@db&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;zincrby&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;weight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;linked_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;suggestions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;options&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;cutoff&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:cutoff&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;limit&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:limit&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;50&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;tmp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tmp2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tmp_key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tmp_key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;related_keys&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@db&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;collect&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;related_keys&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;res&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@db&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;multi&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;zunionstore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tmp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;related_keys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:aggregate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:sum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;zrem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tmp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;zunionstore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tmp2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tmp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:weights&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                                          &lt;span class=&quot;ss&quot;&gt;:aggregate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:min&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;del&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tmp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;zremrangebyscore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tmp2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;-inf&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cutoff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;zrevrange&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tmp2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;limit&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;del&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tmp2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;    
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Example&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Recommendation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;new&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bought&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;b&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;and_also_bought&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;c&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bought&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;d&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;and_also_bought&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;c&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bought&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;e&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;and_also_bought&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;f&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bought&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;a&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;and_also_bought&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;b&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bought&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;a&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;and_also_bought&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;d&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bought&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;a&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;and_also_bought&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;e&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;suggestions_for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;a&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;



</content>
  </entry>
  
  <entry>
    <title>Pagination and HTTP caching</title>
    <link href="http://00f.net/2011/02/25/pagination-and-http-caching/"/>
   <updated>2011-02-25T00:00:00+01:00</updated>
   <id>http://00f.net/2011/02/25/pagination-and-http-caching</id>
   <content type="html">&lt;p&gt;Pagination is a simple concept, but from the developer's side, it's
really tricky to get right.&lt;/p&gt;

&lt;p&gt;Adding pagination to a fixed set of documents is a piece of cake. In
such a situation, lookups have no valid reason not to be O(1) unless
you're blindly using SELECT...LIMIT SQL statements.&lt;/p&gt;

&lt;p&gt;But paginating dynamic data, where documents are constantly added,
removed and modified, is slightly more challenging. Alas, discussion
boards, blog engines and social networks have to suck it up.&lt;/p&gt;

&lt;p&gt;In this post, I will focus on client-side caching of pagination.
Googling for &quot;pagination http cache&quot; doesn't yield anything but
articles about server-side caching.&lt;/p&gt;

&lt;p&gt;This is a sad state of affairs. While browsing a blog or a forum,
going back and forth between pages is an instinctive behavior. A lack
of client-side caching significantly degrades a user's experience,
is a waste of bandwidth and burns server CPU cycles for no valid
reasons.&lt;/p&gt;

&lt;h2&gt;Don't do this at home&lt;/h2&gt;

&lt;p&gt;Wordpress, vBulletin, Ning and unfortunately a lot of other forum and blog
engines and services, are using the worst possible approach.&lt;/p&gt;

&lt;p&gt;Document A is posted. Later on comes document B and a bunch of other
documents: C, D, E, and so on. Let's suppose document R is the
freshest one and pages are designed to hold up to 5 documents.&lt;/p&gt;

&lt;p&gt;Browsing http://example.com displays the newest 5 documents: R, Q, P,
O, N. Good and totally expected.&lt;/p&gt;

&lt;p&gt;Things start to go wrong when a user follows the &quot;see older posts&quot;
link, leading to the next page.&lt;/p&gt;

&lt;p&gt;R, Q, P, O and N are on http://example.com, which is an alias for
http://example.com/page1
M, L, K, J, and I are served as http://example.com/page2.
H, G, F, E, and D are served as http://example.com/page3 and so on.&lt;/p&gt;

&lt;p&gt;So, as the page number grows, the content is getting older.&lt;/p&gt;

&lt;p&gt;This is terrible.&lt;/p&gt;

&lt;p&gt;Inserting a new document S means means that &lt;em&gt;all&lt;/em&gt; pages immediately become
inconsistent.&lt;/p&gt;

&lt;p&gt;http://example.com should now hold S, R, Q, P and O.
http://example.com/page1 should now hold N, M, L K and J.
http://example.com/page2 should now hold I, H, G, F and E.&lt;/p&gt;

&lt;p&gt;And this is even more terrible because inserting new documents is an
operation that obviously has a high chance of being constantly
performed.&lt;/p&gt;

&lt;p&gt;This kind of pagination is inefficient. Users can't reliably bookmark
a page, because if they open the bookmark later on, they're might
reach other documents.&lt;/p&gt;

&lt;p&gt;It ruins SEO. And to top it off, it makes pages virtually impossible
to cache.&lt;/p&gt;

&lt;p&gt;Wordpress, vBulletin, Ning and others are solving this by explicitly
preventing a browser to cache a page.&lt;/p&gt;

&lt;p&gt;So if a user reads page 1, then goes to page 2, then goes back to page
1, downloading the full page 1 will be necessary, even if nothing
has changed at all.&lt;/p&gt;

&lt;p&gt;Granted, there are &quot;cacheability&quot; addons for Wordpress and vBulletin,
but all they do is to sometimes issue a &quot;304 Not Modified&quot; reply
instead of providing URIs featuring a decent time to live.&lt;/p&gt;

&lt;p&gt;How come having to rewrite every page when something is added to a text is
something that never happens in the real life?&lt;/p&gt;

&lt;p&gt;Well... the usual way to use a notebook is to start to write at page 1.
Once page 1 is full, the pen holder keeps writing, but on page 2. Once
page 2 is full, he keeps writing, that time on page 3.
Thus, page 1 holds the oldest content and the last page of the book
holds the content that was written last.&lt;/p&gt;

&lt;p&gt;Sounds pretty straightforward, eh?&lt;/p&gt;

&lt;p&gt;So why not do so on a web site?&lt;/p&gt;

&lt;h2&gt;The first article id&lt;/h2&gt;

&lt;p&gt;Page numbers were designed for books. More specifically, they were
designed in order to manually find content according to a table of
contents.&lt;/p&gt;

&lt;p&gt;But on a computer, we never have to flick over pages in order to find
the content associated to a link. We just click a link, or touch the
screen.&lt;/p&gt;

&lt;p&gt;Hence, there's no valid reasons to use a page number in order to refer
to a page.&lt;/p&gt;

&lt;p&gt;Overblog are using URIs like:&lt;/p&gt;

&lt;p&gt;http://example.com/40-index.html&lt;/p&gt;

&lt;p&gt;40 doesn't mean page 40. It actually means &quot;the page whose first
article is the 40th&quot;. So /40-index.html holds documents 40, 41, 42,
43, 44 and the next 5-items page is /45-index.html.&lt;/p&gt;

&lt;p&gt;An immediate benefit is that if a blog owner changes the number of
articles per page, it doesn't break anything. Previously indexed /
bookmarked / cached URIs remain totally valid, although the number of
articles per page might not be initially consistent.&lt;/p&gt;

&lt;p&gt;However, unless explicitely configured the other way round, document
40 means the 40th starting from the most recent one. Publish a new
document, and ex-document 40 now refers to another document, and
/40-index.html is out of date.&lt;/p&gt;

&lt;p&gt;Overblog pages uses ETags, that properly change whenever the content
of a page changes. Still, even if the content hasn't changed, a
useless round-trip with a 304 reply is required every time.&lt;/p&gt;

&lt;p&gt;Things would have been much better if document identifiers were
incremental, ie. new documents get a higher id than older ones.&lt;/p&gt;

&lt;p&gt;00f.net's initial blog engine worked that way. Pages URIs were made of
timestamps:&lt;/p&gt;

&lt;p&gt;http://example.com/1298640310.html&lt;/p&gt;

&lt;p&gt;The content of such a page is N documents whose timestamp is greater
than or equal to 1298640310.&lt;/p&gt;

&lt;p&gt;This scheme works pretty well, because pages can get easily cached and
indexed. Pages that don't get any update can keep the same URI forever.&lt;/p&gt;

&lt;p&gt;An article added to the freshest page automatically causes the URI to
change, without invalidating the previous URI.&lt;/p&gt;

&lt;p&gt;Timestamps were good enough for this blog, but any increasing and globally
unique identifier would be a good fit.&lt;/p&gt;

&lt;p&gt;The downside of non-monotonically increasing ids is that while it's
easy to jump to older content (&quot;next page&quot;), it can be tricky to get
links to previous pages (newer content) without falling into duplicate
content or HTTP redirects. But for infinite scroll pagination style,
it makes wonders.&lt;/p&gt;

&lt;h2&gt;Dealing with update and removal&lt;/h2&gt;

&lt;p&gt;Unfortunately, adding new documents is not the only change that is
going to occur. Updates and deletions are also bound to happen, albeit
less frequently.&lt;/p&gt;

&lt;p&gt;How to deal with that?&lt;/p&gt;

&lt;p&gt;Well, if a different version of the content is associated to the same
page identifier, just bump the version up and include this version
very number in the URI.&lt;/p&gt;

&lt;p&gt;http://example.com/v2/5/&lt;/p&gt;

&lt;p&gt;If a request to http://example.com/v1/5/ ever comes in, a permanent
redirect to http://example.com/v2/5/ can be issued. In any case,
document 5 (+ older ones) will be present on the page, and browser
cache will get invalidated.&lt;/p&gt;

&lt;p&gt;While finer versioning (per-page group) would be achievable, a global
version number is way easier to deal with. And good enough, considering the
fact that updates and deletions are way less frequent than newly added content.&lt;/p&gt;

&lt;p&gt;If having a version number in the URI of the first page, ie. the one with the
most recent documents, is a concern, this page might be handled
differently by falling back to Last Modified + 304 replies.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Thoughts on PHP sessions</title>
    <link href="http://00f.net/2011/01/19/thoughts-on-php-sessions/"/>
   <updated>2011-01-19T00:00:00+01:00</updated>
   <id>http://00f.net/2011/01/19/thoughts-on-php-sessions</id>
   <content type="html">&lt;p&gt;The sessions mechanism is one of the oldest component in PHP. It was
born in 1997 and it remains pretty much the same in PHP 5.3, minus a
severe security flaw that has been fixed.&lt;/p&gt;

&lt;p&gt;Still, it remains widely used, possibly overused and misused as
today's web sites aren't exactly facing the same challenges as sites
made 15 years ago.&lt;/p&gt;

&lt;p&gt;A PHP session is like a super-global array. The content of this array
is automatically serialized and saved as a temporary file or in a
database. A key, known as a &quot;session identifier&quot; is sent to the
client. Further requests can include this key (usually as a cookie) in
order to have PHP load the file or query the database, and fill back
the super-global array.&lt;/p&gt;

&lt;p&gt;This is a very bare bone mechanism.&lt;/p&gt;

&lt;p&gt;Here's the typical flow of execution of a PHP script using sessions.&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;php&quot;&gt;&lt;span class=&quot;x&quot;&gt;session_start();&lt;/span&gt;
&lt;span class=&quot;x&quot;&gt;# PHP reads a cookie (like PHPSESSID)&lt;/span&gt;
&lt;span class=&quot;x&quot;&gt;# PHP reads a file whose name is based on the cookie value&lt;/span&gt;
&lt;span class=&quot;x&quot;&gt;# PHP unserializes the content, and stores it into $_SESSION&lt;/span&gt;
&lt;span class=&quot;x&quot;&gt;# PHP adds a PHPSESSID cookie to the forthcoming reply&lt;/span&gt;

&lt;span class=&quot;x&quot;&gt;tons_of_things();&lt;/span&gt;

&lt;span class=&quot;x&quot;&gt;# PHP serializes $_SESSION and stores it in the session file&lt;/span&gt;
&lt;span class=&quot;x&quot;&gt;# The output buffer is flushed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;


&lt;p&gt;The content of $_SESSION can be written before the end of the script
if session_commit() is explicitly called.&lt;/p&gt;

&lt;p&gt;More or less randomly, an additional function is called. That one
is responsible for removing expired session files. Instead of hurting
arbitrary requests, some Linux distributions delegate the cleanup
to a cron-driven shell script.&lt;/p&gt;

&lt;h1&gt;To lock or not to lock?&lt;/h1&gt;

&lt;p&gt;When session_start() is called, the default session handler opens or
create a session file and immediately locks it for exclusive use (see
ext/session/mod_files.c).&lt;/p&gt;

&lt;p&gt;This file gets unlocked when session_commit() is called, either
explicitly, or implicitly at the end of the script.&lt;/p&gt;

&lt;p&gt;This locking has a significant impact: parallel requests on PHP
scripts using sessions will not be processed in parallel, but
sequentially.&lt;/p&gt;

&lt;p&gt;If a document is being served to a user, another session-enabled
request from the same user will block. session_start() will block
until the previous document has been fully served.&lt;/p&gt;

&lt;h2&gt;Why locking is good&lt;/h2&gt;

&lt;p&gt;Handling things sequentially and not in parallel is an obvious way to
avoid race conditions.&lt;/p&gt;

&lt;p&gt;A lot of PHP applications don't pay attention to atomicity and integrity.
Databases operations are made without any transactions. Referencial
integrity is not enforced. Denormalization makes it worse.&lt;/p&gt;

&lt;p&gt;Consider the following pseudo-code, inspired by a popular discussion
board engine:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;php&quot;&gt;&lt;span class=&quot;x&quot;&gt;function grant_user_access_to_section() {&lt;/span&gt;
&lt;span class=&quot;x&quot;&gt;  add_user_to_section();&lt;/span&gt;
&lt;span class=&quot;x&quot;&gt;  update_user_status();  &lt;/span&gt;
&lt;span class=&quot;x&quot;&gt;  update_section_acls();&lt;/span&gt;
&lt;span class=&quot;x&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;


&lt;p&gt;Without any transactions, there's an obvious and possibly dangerous
race condition. A user could load another document in another window
while the database is still in a half-assed state.&lt;/p&gt;

&lt;p&gt;Session locking indirectly protects against this.&lt;/p&gt;

&lt;p&gt;Another scenario to consider is this one:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The main page for an ecommerce web site starts to load and gets
partially rendered by the browser. The script reads $_SESSION in order to
display the current number of items in the basket,&lt;/li&gt;
&lt;li&gt;Before the request is complete, the user clicks &quot;add to shopping
cart&quot;,&lt;/li&gt;
&lt;li&gt;This triggers an AJAX request that adds the new item to the
$_SESSION-powered basket,&lt;/li&gt;
&lt;li&gt;The script for the main page completes.&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;Locking ensures that the script called through AJAX will block until the script
for the main page completes.&lt;/p&gt;

&lt;p&gt;Sure, this prevents race conditions. But from a user point of view, it
sucks. It means that nothing will immediatly happen after the user
clicks &quot;add to shopping cart&quot;. The request will stall until
session_start() unblocks. From a sysadmin point of view, it also
sucks. Having full processes doing nothing but wait for a lock sucks.
From a developer point of view, it also sucks. Serialization was
acceptable when applications were designed for a single computer running a
single core. Now, we're living in a massively parallel world and
every time you use a global lock, God asks Cuteoverload to remove
pictures of painfully cute kittens.&lt;/p&gt;

&lt;p&gt;What would happen without locking?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The script for the main page loads the session file, stores the
content into $_SESSION,&lt;/li&gt;
&lt;li&gt;The script called through AJAX loads the session file, stores the
content into $_SESSION,&lt;/li&gt;
&lt;li&gt;The script called through AJAX adds an item to $_SESSION&lt;/li&gt;
&lt;li&gt;The script called through AJAX saves $_SESSION as the session file,&lt;/li&gt;
&lt;li&gt;The script for the main page saves $_SESSION as the session file.&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;A script is unaware of what another script is doing at the same time.
So the script from the main page will overwrite the session file with
the content it previously read, no matter what the other script did
inbetween. The item won't be added to the shopping card.&lt;/p&gt;

&lt;p&gt;But... is the lack of locking the real issue? Or is locking just a
kludgy band-aid against bad code, a la magic_quotes?&lt;/p&gt;

&lt;p&gt;Let's recap what's happening in the script for the main page:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The session file is loaded&lt;/li&gt;
&lt;li&gt;$_SESSION is build, by unserializing the content of the session file&lt;/li&gt;
&lt;li&gt;A single value (the number of items in the cart) is read from
$_SESSION&lt;/li&gt;
&lt;li&gt;The content of the file is overwritten with the content of $_SESSION.&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;Can't you spot anything wrong here, way more shocking that anything
that has to do with locking?&lt;/p&gt;

&lt;p&gt;First, $_SESSION is a single big bucket. Its whole content is read and
written from/to the session file everytime. This is the worst possible
ORM. Totally unrelated content is going to get packed in the same
basket. A script whose purpose is to add a product to the basket can
overwrite a captcha key, just because if both use the PHP session,
they share the same storage space, which is completely
reread/rewritten by every script every time.&lt;/p&gt;

&lt;p&gt;Another shocking aspect is the fact that our main script was supposed
to only &lt;em&gt;read&lt;/em&gt; the session.
Yet, at the end of the script, PHP will &lt;em&gt;overwrite&lt;/em&gt; the session file.
And guess what with? The content it just read, unmodified. The file
should be touched, not overwritten.
Best of all, the main script, that doesn't change anything to the
session, blocks every other script waiting for the session.&lt;/p&gt;

&lt;p&gt;Is locking the solution? No.&lt;/p&gt;

&lt;h2&gt;Why no lock is better&lt;/h2&gt;

&lt;p&gt;The HTTP protocol has been designed to be stateless. And it works
wonderfully this way.&lt;/p&gt;

&lt;p&gt;Serving totally different content according to unknown data stored in
server-side sessions easily breaks the REST principles. If you don't
care about the REST principles, consider that: relying on sessions is
likely to ruin the cacheability of what you are serving. Serving
the same content when asking for the same thing is the key to
cacheability. And possibly what the HTTP protocol has been designed for.&lt;/p&gt;

&lt;p&gt;Ditching locks means that requests can be handled in parallel, not
sequentially.&lt;/p&gt;

&lt;p&gt;A web site that cares a little about performance uses progressive
loading. Fragments are rendered independently (and possibly
reassembled as a single page through Varnish or Nginx for search
engines, but we're disgressing).&lt;/p&gt;

&lt;p&gt;If every fragment has to wait for the previous one to complete, the
benefits in progressive loading can vanish. Parallelization, hence
being lockless, is important.&lt;/p&gt;

&lt;p&gt;No lock means no idle process, doing nothing but waiting for a lock
being released. PHP is not event-driven. Blocked scripts can't be a good
thing.&lt;/p&gt;

&lt;p&gt;Going lockless also means that, in order to provide sessions that can
be shared by multiple PHP servers, a bunch of options are available.
Virtually any key/value data store can fit the bill: Redis, Membase,
Kyoto Tycoon, you name it.&lt;/p&gt;

&lt;h2&gt;How to safely move from lockful sessions to lockless sessions?&lt;/h2&gt;

&lt;p&gt;Here's a simple rule: use a session as a cache for immutable data.&lt;/p&gt;

&lt;p&gt;If your server-side scripts constantly need to know what the user name is,
it might be acceptable to keep this value in the session.&lt;/p&gt;

&lt;p&gt;Regarding the above example: use dedicated storage space in order to
store the shopping cart. Just create a shopping cart entry for the
current user in Redis, MongoDb, Riak, PostgreSQL, whatever. And push items
there. You don't need to store the shopping cart in the session. You
just need a key to retrieve the shopping cart. And this key may be
kept in the session.&lt;/p&gt;

&lt;p&gt;Respecting the REST principles makes this obvious. The URI for a
shopping cart should be something like /cart/shopping-cart-id, not
/cart for everybody, leading to a document that solely depends on the
session data.&lt;/p&gt;

&lt;p&gt;One URI is one resource. Having a single global array, holding mutable
data from independent resources is total nonsense. If data have to be
shared by different resources, they should be immutable. This is what
a session is for.&lt;/p&gt;

&lt;h1&gt;Ditching PHP sessions&lt;/h1&gt;

&lt;p&gt;Are PHP sessions actually required after all?&lt;/p&gt;

&lt;p&gt;If you have to store secret data that have to be hidden to clients,
maybe. Although you need to double check that a session is really
where you want to store them, ie. that these data will be constantly
needed by a lot of scripts.&lt;/p&gt;

&lt;p&gt;In almost every case, sessions are not needed at all.&lt;/p&gt;

&lt;p&gt;How does a PHP session work? The client sends a cookie, a PHP
script reads this cookie and fetches the session data, using the
cookie value as a key.&lt;/p&gt;

&lt;p&gt;Why not just keep the session data in the cookie?&lt;/p&gt;

&lt;p&gt;This is a very common practice in the Ruby world, introduced a long
time ago with Rails and Rack::Session::Cookie.&lt;/p&gt;

&lt;p&gt;And this is fairly secure as long as the cookie also contains a HMAC
for the data.&lt;/p&gt;

&lt;p&gt;What are the benefits?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fast!&lt;/li&gt;
&lt;li&gt;No need to store any sessions,&lt;/li&gt;
&lt;li&gt;No need to wait for two queries in every script,&lt;/li&gt;
&lt;li&gt;One less point of failure,&lt;/li&gt;
&lt;li&gt;Share-nothing: any client can connect to any PHP server, at any time,&lt;/li&gt;
&lt;li&gt;Having sessions lasting 5 minutes or 5 years don't make any
differences, server-side.&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;Downsides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If the secret key is leaked, you're screwed.&lt;/li&gt;
&lt;li&gt;Larger requests.&lt;/li&gt;
&lt;/ul&gt;


&lt;h1&gt;Cachecookie&lt;/h1&gt;

&lt;p&gt;Cachecookie is a simple way to use cookie-based sessions.&lt;/p&gt;

&lt;p&gt;If you want to give it a shot, just &lt;a href=&quot;http://download.pureftpd.org/misc/class.cache_cookie.inc.php&quot;&gt;download
Cachecookie&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;(I'm not convinced that it is worth pushing to Github, but if you really want to
fork it, ping me).&lt;/p&gt;

&lt;p&gt;Cachecookie lets you store a multiples values in a single cookie,
signed with a HMAC.&lt;/p&gt;

&lt;p&gt;Need to store values? Say the user name and his cart id?&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;php&quot;&gt;&lt;span class=&quot;x&quot;&gt;CacheCookie::set(&amp;#39;user_name&amp;#39;, &amp;#39;John&amp;#39;, 86400);&lt;/span&gt;
&lt;span class=&quot;x&quot;&gt;CacheCookie::set(&amp;#39;cart_id&amp;#39;, &amp;#39;deadbeef&amp;#39;, 31536000);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;


&lt;p&gt;The third argument is the life time.
Yes. Cachecookie lets you store multiple values, with individual expiration
dates, in a single cookie. Ain't it great?&lt;/p&gt;

&lt;p&gt;Let's retrieve the user name:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;php&quot;&gt;&lt;span class=&quot;x&quot;&gt;$user_name = CacheCookie::get(&amp;#39;user_name&amp;#39;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;


&lt;p&gt;If no entry is found, all you get is NULL.&lt;/p&gt;

&lt;p&gt;Of course, the signature of the cookie is verified before any operation.&lt;/p&gt;

&lt;p&gt;Want to delete a value?&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;php&quot;&gt;&lt;span class=&quot;x&quot;&gt;CacheCookie::delete(&amp;#39;user_name&amp;#39;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;


&lt;p&gt;The method returns TRUE if the key existed, FALSE if you attempted to
delete something nonexistent.&lt;/p&gt;

&lt;p&gt;Want to delete everything (in fact, the single cookie)?&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;php&quot;&gt;&lt;span class=&quot;x&quot;&gt;CacheCookie::delete_all();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;


&lt;p&gt;Have fun!&lt;/p&gt;
</content>
  </entry>
  
</feed>


