<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Alex Friedman</title>
    <description>The latest articles on DEV Community by Alex Friedman (@ahf90).</description>
    <link>https://dev.to/ahf90</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F485694%2F67160f8b-003b-4ffc-951e-1e46a28501ca.png</url>
      <title>DEV Community: Alex Friedman</title>
      <link>https://dev.to/ahf90</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ahf90"/>
    <language>en</language>
    <item>
      <title>Sorting by external keys in Redis with Python</title>
      <dc:creator>Alex Friedman</dc:creator>
      <pubDate>Wed, 21 Oct 2020 21:11:43 +0000</pubDate>
      <link>https://dev.to/ahf90/sorting-by-external-keys-in-redis-with-python-569k</link>
      <guid>https://dev.to/ahf90/sorting-by-external-keys-in-redis-with-python-569k</guid>
      <description>&lt;p&gt;Sometimes you want to sort elements using external keys as weights to compare instead of comparing the actual elements in the list, set or sorted set. Let's say the list &lt;code&gt;indices&lt;/code&gt; contains the elements &lt;code&gt;key0&lt;/code&gt;, &lt;code&gt;key1&lt;/code&gt;, and &lt;code&gt;key3&lt;/code&gt; representing unique IDs of objects stored in as &lt;code&gt;key0&lt;/code&gt;, &lt;code&gt;key1&lt;/code&gt;, and &lt;code&gt;key3&lt;/code&gt;. When these objects have associated weights stored in their hash, SORT can be instructed to use these weights to sort &lt;code&gt;indices&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; from random import randint
&amp;gt;&amp;gt;&amp;gt; import redis
&amp;gt;&amp;gt;&amp;gt; r = redis.Redis(host='localhost', port=6379)

&amp;gt;&amp;gt;&amp;gt; for i in range(3):
...     key = f'key{i}'
...     r.hset(key, 'weight', randint(0, 100))
...     r.sadd('indices', key)

&amp;gt;&amp;gt;&amp;gt; # Show the weights of each Hash
&amp;gt;&amp;gt;&amp;gt; [(f'key{i}', r.hget(f'key{i}', 'weight')) for i in range(3)]
[('key0', b'91'), ('key1', b'40'), ('key2', b'63')]

&amp;gt;&amp;gt;&amp;gt; r.sort('indices', by='*-&amp;gt;weight')
[b'key1', b'key2', b'key0']
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>redis</category>
      <category>python</category>
    </item>
    <item>
      <title>Atomically popping multiple items from a Redis list in Python</title>
      <dc:creator>Alex Friedman</dc:creator>
      <pubDate>Wed, 14 Oct 2020 00:10:17 +0000</pubDate>
      <link>https://dev.to/ahf90/atomically-popping-multiple-items-from-a-redis-list-in-python-2afa</link>
      <guid>https://dev.to/ahf90/atomically-popping-multiple-items-from-a-redis-list-in-python-2afa</guid>
      <description>&lt;h1&gt;
  
  
  What are we doing?
&lt;/h1&gt;

&lt;p&gt;We're trying to atomically pop and remove multiple items from a list in Redis. &lt;/p&gt;

&lt;h2&gt;
  
  
  What?
&lt;/h2&gt;

&lt;p&gt;Consider a list: &lt;code&gt;0 1 2 3 4 5&lt;/code&gt;&lt;br&gt;
I want to return the results of the first 3 items and then remove those items.  I also need to guarantee that the list does not change in the time between returning the results and remove the results from the list.&lt;/p&gt;
&lt;h2&gt;
  
  
  An example
&lt;/h2&gt;

&lt;p&gt;Consider this example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; RPUSH mykey 0 1 2 3 4 5
(integer) 6
&amp;gt; LRANGE mykey 0 2
1) "0"
2) "1"
3) "2"
&amp;gt; LTRIM mykey 3 -1
OK
&amp;gt; LRANGE mykey 0 99
1) "3"
2) "4"
3) "5"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works well if you are the only one manipulating your list.  But what if somebody else is manipulating the list at the same time?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; RPUSH mykey 0 1 2 3 4 5
(integer) 6
&amp;gt; LRANGE mykey 0 2
1) "0"
2) "1"
3) "2"
&amp;gt; LTRIM mykey 3 -1
OK
&amp;gt; LRANGE mykey 0 99
(empty array)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Uh-oh.  Another consumer, who we will call B, ran &lt;code&gt;LRANGE mykey 0 2&lt;/code&gt; and received &lt;code&gt;0 1 2&lt;/code&gt;. We ran the same command immediately after them and received the same results.  Consumer B now runs &lt;code&gt;LTRIM mykey 3 -1&lt;/code&gt;, and we do too.  Now the list is empty, but both consumers only received the first 3 items of the list.  The final 3 are gone.&lt;/p&gt;

&lt;h1&gt;
  
  
  Solution
&lt;/h1&gt;

&lt;p&gt;I need these two transactions, &lt;code&gt;LRANGE&lt;/code&gt; and &lt;code&gt;LTRIM&lt;/code&gt;, to be atomic.  That means that either they both execute, or neither does.  In Redis, we can achieve this using &lt;a href="https://redis.io/topics/transactions"&gt;transactions&lt;/a&gt;. All the commands in a transaction are serialized and executed sequentially. It can never happen that a request issued by another client is served in the middle of the execution of a Redis transaction. This guarantees that the commands are executed as a single isolated operation.&lt;/p&gt;

&lt;p&gt;If you were using the Redis CLI, you would alter your code to look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; MULTI
OK
&amp;gt; RPUSH mykey 0 1 2 3 4 5
QUEUED
&amp;gt; LTRIM mykey 3 -1
QUEUED
&amp;gt; EXEC
1) (integer) 6
2) OK
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  In Python
&lt;/h2&gt;

&lt;p&gt;Since nobody actually uses CLIs, let's do this in Python.  This script uses &lt;a href="https://redis-py-doc.readthedocs.io/"&gt;redis-py&lt;/a&gt;'s Pipeline feature. Pipelines are a subclass of the base Redis class that provide support for buffering multiple commands to the server in a single request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; import redis
&amp;gt;&amp;gt;&amp;gt; my_key = 'pop_trim_test'
&amp;gt;&amp;gt;&amp;gt; r = redis.Redis(host='localhost', port=6379)
&amp;gt;&amp;gt;&amp;gt; r.rpush(my_key, *[x for x in range(10)])
&amp;gt;&amp;gt;&amp;gt; pipe = r.pipeline()
&amp;gt;&amp;gt;&amp;gt; pipe.lrange(my_key, 0, 3)
&amp;gt;&amp;gt;&amp;gt; pipe.ltrim(my_key, 4, -1)
&amp;gt;&amp;gt;&amp;gt; pipe.execute()
[[b'0', b'1', b'2', b'3'], True]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;pipe.execute()&lt;/code&gt; sequentially returns the results of your pipe commands as a list.  Therefore, the results of our &lt;code&gt;lrange&lt;/code&gt; are the first item in the list.&lt;/p&gt;

&lt;h3&gt;
  
  
  Even better
&lt;/h3&gt;

&lt;p&gt;If you're working with a consumer model where applications wait for items in a queue, the above code can be problematic because it is constantly querying Redis.  A better behavior is to use &lt;a href="https://redis.io/commands/blpop"&gt;Redis' blocking pop (BLPOP) function&lt;/a&gt;.  With BLPOP, if the key is empty, the client  blocks the connection until another client performs an LPUSH or RPUSH operation against the key.  It's a great resource for handling multiple consumers.  You can work this in by running the above code &lt;em&gt;after&lt;/em&gt; the consumer successfully executes a BLPOP.&lt;/p&gt;

</description>
      <category>redis</category>
      <category>python</category>
      <category>atomicity</category>
    </item>
    <item>
      <title>Discover pods by label in Prometheus</title>
      <dc:creator>Alex Friedman</dc:creator>
      <pubDate>Mon, 12 Oct 2020 18:00:54 +0000</pubDate>
      <link>https://dev.to/ahf90/discover-pods-by-label-in-prometheus-2ldm</link>
      <guid>https://dev.to/ahf90/discover-pods-by-label-in-prometheus-2ldm</guid>
      <description>&lt;h1&gt;
  
  
  What are we doing?
&lt;/h1&gt;

&lt;p&gt;Kubernetes SD configurations allow retrieving scrape targets from Kubernetes' REST API in order to stay synchronized with the cluster state.&lt;/p&gt;

&lt;p&gt;I created an image of a Python program that exports metrics using the &lt;a href="https://github.com/prometheus/client_python" rel="noopener noreferrer"&gt;the Prometheus Python client&lt;/a&gt;. Setting the environment variable &lt;code&gt;EXPORTER_PORT&lt;/code&gt; will publish metrics to that port.&lt;/p&gt;

&lt;p&gt;I deployed this container in pods with labels &lt;code&gt;foo&lt;/code&gt; and &lt;code&gt;bar&lt;/code&gt; and sent metrics to port 9000 and 8000 respectively. You can see in the below Prometheus config that I target them separately by their label name.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why would you need to do this?
&lt;/h1&gt;

&lt;p&gt;I found a case where I wanted different scrape configurations per pod in my cluster.  Usually, I can set the &lt;code&gt;kubernetes_sd_configs&lt;/code&gt; role to &lt;code&gt;service&lt;/code&gt;, which discovers a target for each service port for each service (see the &lt;a href="https://prometheus.io/docs/prometheus/latest/configuration/configuration/#kubernetes_sd_config" rel="noopener noreferrer"&gt;docs&lt;/a&gt;).  However, in this case I had different collection requirements for the pods inside my k8s abstractions.&lt;/p&gt;

&lt;h1&gt;
  
  
  Example config files
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Foo Deployment
&lt;/h2&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

apiVersion: apps/v1
kind: Deployment
metadata:
  name: foo-deployment
  labels:
    app: foo
spec:
  selector:
    matchLabels:
      app: foo
  replicas: 2
  template:
    metadata:
      labels:
        app: foo
    spec:
      containers:
      - name: python-prometheus-exporter-example
        image: ahf90/python-prometheus-exporter-example
        imagePullPolicy: Always
        ports:
          - containerPort: 9000
        env:
          - name: EXPORTER_PORT
            value: '9000'


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Bar Deployment
&lt;/h2&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

apiVersion: apps/v1
kind: Deployment
metadata:
  name: bar-deployment
  labels:
    app: bar
spec:
  selector:
    matchLabels:
      app: bar
  replicas: 2
  template:
    metadata:
      labels:
        app: bar
    spec:
      containers:
      - name: python-prometheus-exporter-example
        image: ahf90/python-prometheus-exporter-example
        imagePullPolicy: Always
        ports:
          - containerPort: 8000
        env:
          - name: EXPORTER_PORT
            value: '8000'


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Prometheus config.yml
&lt;/h2&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

scrape_configs:
  - job_name: 'prometheus'
    static_configs:
    - targets: ['localhost:9090']
  - job_name: 'foo-targets'
    scrape_interval: 10s
    metrics_path: '/'
    kubernetes_sd_configs:
    - role: pod
      namespaces:
        names:
        - default
      selectors:
      - role: "pod"
        label: "app=foo"
  - job_name: 'bar-targets'
    scrape_interval: 5s
    metrics_path: '/'
    kubernetes_sd_configs:
      - role: pod
        namespaces:
          names:
            - default
        selectors:
          - role: "pod"
            label: "app=bar"


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fmpluuaheqfnn8fjvlmg5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fmpluuaheqfnn8fjvlmg5.png" alt="Prometheus targets page"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>prometheus</category>
      <category>kubernetes</category>
      <category>monitoring</category>
      <category>python</category>
    </item>
  </channel>
</rss>
