<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[JaM2in - Medium]]></title>
        <description><![CDATA[A collection of technical articles and blogs about ARCUS published or curated by JaM2in Developer. - Medium]]></description>
        <link>https://medium.com/jam2in?source=rss----c55eb3541815---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>JaM2in - Medium</title>
            <link>https://medium.com/jam2in?source=rss----c55eb3541815---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 24 May 2026 02:28:21 GMT</lastBuildDate>
        <atom:link href="https://medium.com/feed/jam2in" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[IDC 장애를 넘어서는 무중단 ARCUS 캐시 클러스터]]></title>
            <link>https://medium.com/jam2in/idc-%EC%9E%A5%EC%95%A0%EB%A5%BC-%EB%84%98%EC%96%B4%EC%84%9C%EB%8A%94-%EB%AC%B4%EC%A4%91%EB%8B%A8-arcus-%EC%BA%90%EC%8B%9C-%ED%81%B4%EB%9F%AC%EC%8A%A4%ED%84%B0-e51c2cdac3ed?source=rss----c55eb3541815---4</link>
            <guid isPermaLink="false">https://medium.com/p/e51c2cdac3ed</guid>
            <category><![CDATA[fault-tolerance]]></category>
            <category><![CDATA[caching]]></category>
            <category><![CDATA[high-availability]]></category>
            <category><![CDATA[arcus-cache-cluster]]></category>
            <category><![CDATA[distributed-systems]]></category>
            <dc:creator><![CDATA[JaM2in]]></dc:creator>
            <pubDate>Thu, 16 Apr 2026 07:00:06 GMT</pubDate>
            <atom:updated>2026-04-16T07:00:07.840Z</atom:updated>
            <content:encoded><![CDATA[<p>오늘날 수많은 웹/모바일 서비스들은 사용자에게 빠르고 안정적인 경험을 제공하기 위해 In-memory Cache를 필수적으로 사용합니다. 그럼에도 불구하고 캐시 클러스터에 장애가 발생하면 서비스의 응답 속도가 급격히 저하되거나, 심지어는 서비스 자체가 불가능해질 수 있습니다. 특히, IDC(데이터 센터) 전체에 장애가 발생하더라도 서비스 연속성을 보장하는 것은 현대 서비스 아키텍처의 핵심 과제 중 하나입니다.</p><p>memcached와 ZooKeeper 기반의 분산형 캐시 서비스인 ARCUS는 IDC 장애를 대처하기에 적합한 방안을 제공합니다. ARCUS는 신속한 장애 감지와 Fail-Stop 메커니즘(아래 상세 설명 참조)을 활용하여 개별 노드의 장애는 물론, IDC 전체가 마비되는 상황에서도 무중단 캐시 서비스를 가능하게 합니다.</p><h3>ARCUS 아키텍처</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1cp0FQwGcQCsxDkgzHA-_A.png" /></figure><p>ARCUS 캐시의 고가용성 비결을 이해하려면 먼저 그 아키텍처를 알아야 합니다. ARCUS는 여러 캐시 노드들을 하나의 클러스터로 묶어 관리하며, 이 클러스터의 상태를 관리하는 핵심적인 역할을 ZooKeeper가 담당합니다.</p><ul><li><strong>중앙 관리자 ZooKeeper</strong>: 클러스터에 속한 Alive 캐시 노드들의 목록을 유지하고 관리합니다.</li><li><strong>노드 등록 및 장애 감지 시 즉시 노드 제거</strong>: 각 캐시 노드는 구동될 때 ZooKeeper에 자신을 등록하여 캐시 클러스터의 일부가 됩니다. ZooKeeper는 주기적으로 각 노드의 상태를 확인하며, 특정 노드에 장애가 발생하여 응답이 없으면 즉시 해당 노드를 즉시 제거하여 클러스터에서 제거되게 합니다.</li><li><strong>클라이언트와 캐시 목록 동기화</strong>: ARCUS 클라이언트(응용 프로그램)는 ZooKeeper로부터 항상 최신 상태의 Alive 캐시 노드 목록을 전달 받습니다. 이를 통해 클라이언트는 장애가 발생한 노드를 제외하고 정상적으로 동작하는 노드들에게만 요청을 보낼 수 있습니다.</li></ul><h3>신속한 장애 감지와 Fail-Stop</h3><p>ARCUS 캐시의 가장 큰 특징 중 하나는 <strong>Fail-Stop</strong> 동작 방식입니다. 이는 장애가 발생한 캐시 노드가 캐시 클러스터 전체에 악영향을 주지 않도록 장애를 격리시키는 방식입니다.</p><p>ZooKeeper가 특정 캐시 노드의 장애를 감지하면, 그 즉시 해당 노드를 서비스 가능한 노드 목록에서 제외하고 이 정보를 클라이언트에게 통지합니다. 클라이언트는 갱신된 노드 목록을 받아 Consistent Hashing을 통해 장애 노드를 제외한 다른 정상 노드에게 캐시 연산을 요청합니다. 이 모든 과정이 자동으로 빠르게 이루어지기 때문에, 운영자의 개입 없이도 장애는 신속하게 격리되고 서비스는 중단 없이 계속될 수 있습니다.</p><h3>Multi-IDC 클러스터 구성과 IDC 장애 극복 시나리오</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*acPBodluoJBrywtJHuzi1Q.png" /></figure><p>ARCUS의 Fail-Stop 동작은 캐시 클러스터를 여러 IDC에 분산하여 구성했을 때에도 유용하게 사용됩니다. 몇몇 실제 운영 환경에서는 가용성을 높이기 위해, 위 그림과 같이 물리적으로 떨어진 여러 IDC에 걸쳐 ARCUS 캐시 클러스터를 구축하여 사용하고 있습니다. 위와 같은 구조에서 IDC A에 전체 장애가 발생할 경우, 캐시 클러스터의 Fault-tolerant 동작을 설명 드리겠습니다.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*AUbVwXd5rnhANCwhU3yP6w.png" /></figure><p><strong>1. IDC A에 전체 장애 발생</strong> : 네트워크 문제, 정전, 자연재해 등으로 인해 IDC A 전체가 오프라인 상태가 됩니다.</p><p><strong>2. 장애 감지</strong> : IDC B, C에 있는 ZooKeeper 노드들은 IDC A의 모든 ZK 노드와 캐시 노드들이 응답하지 않는 것을 감지합니다.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8TnjZ8ML8Xh8z2Iq98_v1g.png" /></figure><p><strong>3. ZooKeeper 쿼럼 동작</strong> : IDC A의 ZooKeeper 노드를 제외하여도, 과반수 이상의 ZooKeeper 노드가 동작하고 있으므로 ZooKeeper는 서비스를 유지할 수 있습니다.</p><p><strong>4. 캐시 클러스터 재구성</strong> : ZooKeeper 앙상블은 장애가 발생한 IDC A의 모든 캐시 노드를 서비스 목록에서 자동으로 제거합니다.</p><p><strong>5. 캐시 서비스 유지</strong> : ARCUS 클라이언트는 ZooKeeper로부터 IDC B, C의 캐시 노드들만 포함된 최신 목록을 전달 받고, 모든 캐시 요청을 IDC B, C의 정상 노드들로만 전송합니다.</p><p>이처럼 ARCUS는 하나의 IDC가 완전히 무너지더라도, 살아남은 다른 IDC의 자원들을 통해 중단 없이 캐시 서비스를 지속할 수 있습니다.</p><h3>데이터 이중화 향후 로드맵 : XDCR</h3><p>ARCUS는 이미 Multi-IDC 환경에서 뛰어난 고가용성을 제공하지만, 더 높은 수준의 데이터 이중화와 DR(재해 복구) 역량을 갖추기 위해 발전하고 있습니다.</p><p>ARCUS의 로드맵에서 가장 기대되는 데이터 이중화 기능은 바로 XDCR(Cross-Datacenter Replication) 입니다. XDCR은 서로 다른 IDC에 위치한 두 개 이상의 ARCUS 캐시 노드 간에 데이터를 복제하는 기술입니다. 이 기능이 도입된다면, 한 쪽 IDC의 데이터가 변경될 때 다른 쪽 IDC의 캐시 노드에도 동일하게 반영되어 데이터 일관성을 유지할 수 있습니다. 하나의 IDC에서 전체 장애가 발생하더라도, XDCR을 통해 다른 IDC의 캐시 노드가 즉시 서비스를 이어받을 수 있어 완벽한 DR 환경을 구축할 수 있게 될 것입니다.</p><h3>고가용성 캐시 인프라</h3><p>ARCUS는 ZooKeeper를 활용한 중앙 집중적인 클러스터 관리와 신속한 Fail-Stop 메커니즘을 통해 뛰어난 장애 대응 능력을 보여줍니다. 특히 여러 IDC에 걸쳐 클러스터를 분산 배치하는 아키텍처를 통해, 데이터 센터 수준의 대규모 장애 상황에서도 서비스의 연속성을 보장하는 강력한 고가용성 환경을 구축할 수 있습니다.</p><p>향후 XDCR과 같은 고급 기능들이 로드맵에 따라 개발되고 적용된다면 ARCUS는 단순히 빠른 캐시를 넘어, 데이터 안정성과 서비스 연속성을 최고 수준으로 보장하는 핵심 인프라로 자리매김할 것입니다. 안정적인 캐시 인프라를 고민하고 있다면, 끊임없이 발전하는 ARCUS는 분명 훌륭한 선택지가 될 것입니다.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e51c2cdac3ed" width="1" height="1" alt=""><hr><p><a href="https://medium.com/jam2in/idc-%EC%9E%A5%EC%95%A0%EB%A5%BC-%EB%84%98%EC%96%B4%EC%84%9C%EB%8A%94-%EB%AC%B4%EC%A4%91%EB%8B%A8-arcus-%EC%BA%90%EC%8B%9C-%ED%81%B4%EB%9F%AC%EC%8A%A4%ED%84%B0-e51c2cdac3ed">IDC 장애를 넘어서는 무중단 ARCUS 캐시 클러스터</a> was originally published in <a href="https://medium.com/jam2in">JaM2in</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Kubernetes 환경에서 ARCUS 캐시 사용하기]]></title>
            <link>https://medium.com/jam2in/kubernetes-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-arcus-%EC%BA%90%EC%8B%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0-50ae837fd966?source=rss----c55eb3541815---4</link>
            <guid isPermaLink="false">https://medium.com/p/50ae837fd966</guid>
            <category><![CDATA[arcus]]></category>
            <category><![CDATA[kubernetes]]></category>
            <category><![CDATA[arcus-cache-cluster]]></category>
            <dc:creator><![CDATA[Namjae Kim]]></dc:creator>
            <pubDate>Tue, 23 Apr 2024 12:06:26 GMT</pubDate>
            <atom:updated>2024-04-23T12:06:26.373Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JVkDmTW73EvmnnIMCPYdvw.png" /></figure><p><a href="https://medium.com/jam2in/docker-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-arcus-%EC%BA%90%EC%8B%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0-aa9dce08210c">Docker 컨테이너 환경에서 ARCUS 캐시 사용하기</a></p><p>이전 포스팅에서는 Docker 컨테이너를 활용해서 하나의 장비에 ARCUS 캐시 클러스터를 구성해 보았습니다. 이번 포스팅에서는 쿠버네티스(Kubernetes)를 활용하여 다수의 호스트 장비에 ARCUS 캐시 클러스터를 구성하는 방법을 소개하겠습니다.</p><h4>Kubernetes</h4><p>쿠버네티스(k8s 또는 kube라고도 함)는 컨테이너화된 워크로드와 서비스를 관리하기 위한 오픈소스 플랫폼입니다. 여러 호스트 장비를 하나의 쿠버네티스 클러스터로 묶어 관리할 수 있고, 이렇게 구성된 쿠버네티스 클러스터에 서비스를 배포하면 적절한 노드(호스트 장비)에 컨테이너를 분산해서 배치하거나, 문제가 생긴 컨테이너를 교체하거나, 컨테이너 동작에 필요한 설정 관리 등의 동작을 수행합니다.</p><p>ARCUS 캐시 클러스터는 가용성과 확장성을 위해 다수의 캐시 노드를 여러 장비에 분산 배치하기 때문에 운영자는 다수의 장비에 프로세스를 적절히 구동하고 설정/관리해 주어야 합니다. 쿠버네티스 환경에서 운영자가 ARCUS 캐시 노드의 수와 분산 배치 전략을 명시하면 쿠버네티스가 그에 맞게 자동으로 분산 배치된 캐시 노드를 구동해주므로 서비스 운영에 드는 노력을 효과적으로 절감할 수 있습니다.</p><blockquote>본 포스팅에서는 다수의 장비에 쿠버네티스 클러스터를 구성하는 대신 minikube를 활용하여 가상의 worker node를 구성하고 실습을 진행해 보겠습니다. 만약 이미 구성된 쿠버네티스 클러스터에서 실습을 진행하려는 경우 minikube 파트는 건너뛰어도 됩니다. 원활한 실습을 위해서는 컨트롤 플레인 노드(마스터 노드) 외에 3개 이상의 워커 노드가 있어야 한다는 점을 유의하세요.</blockquote><blockquote>minikube는 개발 및 학습이 주 목적이기 때문에, 컨트롤 플레인 노드에도 리소스가 스케줄되도록 기본 설정되어 있으니 참고 바랍니다.</blockquote><h4>Minikube</h4><p>실제 운영 환경에서는 다수의 호스트 장비에 걸친 쿠버네티스 클러스터를 구성하고, 각 리소스는 여러 노드에 분산 배치됩니다. 만약 로컬 장비에서 간단한 실습을 진행하려는 경우에는 minikube를 사용하여 다수의 가상 장비를 기반으로 하는 쿠버네티스 클러스터를 구성할 수 있습니다. 실습 환경은 Linux이므로 아래와 같은 명령으로 minikube를 설치할 수 있고, 그 외 환경에서의 설치 방법은 <a href="https://minikube.sigs.k8s.io/docs/start/">minikube Get Started 문서</a>를 참고해 주세요.</p><pre>curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64<br>sudo install minikube-linux-amd64 /usr/local/bin/minikube &amp;&amp; rm minikube-linux-amd64</pre><p>설치가 완료된 후에는 minikube start 명령을 통해 minikube 클러스터를 시작할 수 있습니다. 본 포스팅에서는 ARCUS의 각 구성 요소가 분산 배치되는 것을 확인하기 위해, 총 4개 노드로 구성된 minikube 클러스터를 사용하겠습니다.</p><pre>$ minikube start --nodes=4</pre><p>구성이 완료된 뒤 kubectl get nodes 명령을 사용하면 1개의 control-plane node와 3개의 worker node를 확인하실 수 있습니다. control plane node는 쿠버네티스 클러스터를 관리하는 노드이고, worker node는 사용자가 배포한 서비스를 실행하기 위한 노드입니다.</p><pre>$ kubectl get nodes<br>NAME           STATUS   ROLES           AGE   VERSION<br>minikube       Ready    control-plane   12m   v1.28.3<br>minikube-m02   Ready    &lt;none&gt;          11m   v1.28.3<br>minikube-m03   Ready    &lt;none&gt;          11m   v1.28.3<br>minikube-m04   Ready    &lt;none&gt;          10m   v1.28.3</pre><h4>ZooKeeper ensemble 구성</h4><p>쿠버네티스 클러스터를 준비한 뒤에는 쿠버네티스 환경에서 ARCUS 캐시의 메타 정보를 관리하는 ZooKeeper ensemble을 먼저 구축합니다. 쿠버네티스 튜토리얼인 <a href="https://kubernetes.io/docs/tutorials/stateful-application/zookeeper/">Running ZooKeeper, A Distributed System Coordinator</a>에서 제공하는 manifest 파일을 사용하여 3개 서버로 구성되고 각각은 서로 다른 워커 노드에 배치되는 ZooKeeper ensemble을 구축합니다. 이와 같은 ZooKeeper 서버의 분산 배치를 위하여 쿠버네티스 클러스터에는 3개 이상의 워커 노드가 존재해야 합니다.</p><pre>$ kubectl apply -f https://k8s.io/examples/application/zookeeper/zookeeper.yaml<br>service/zk-hs created<br>service/zk-cs created<br>poddisruptionbudget.policy/zk-pdb created<br>statefulset.apps/zk created</pre><p>manifest 파일에 의해 생성되는 리소스를 살펴보면 다음과 같습니다.</p><ul><li>zk-hs: ZooKeeper 서버 간 통신을 위한 service</li><li>zk-cs: 클라이언트 요청을 처리하기 위한 service</li><li>zk-pdb: 동시에 여러 서버가 Unavailable 상태가 되는 것을 방지하는 PodDisruptionBudget</li><li>zk: Pod를 관리하는 Statefulset</li></ul><p>모든 리소스가 구동되고 나면 kubectl get pods 명령으로 상태를 확인할 수 있습니다. 정상 구동되었다면 3개 Pod가 Ready(1/1), Running 상태일 것입니다. 만약 그 외의 상태에서 장시간 진행되지 않는 Pod가 있다면, kubectl describe pod &lt;pod-name&gt;명령을 통해 문제 원인을 파악하고 조치를 취해야 합니다.</p><pre>$ kubectl get pods -o wide<br>NAME   READY   STATUS    RESTARTS   AGE     IP           NODE           NOMINATED NODE   READINESS GATES<br>zk-0   1/1     Running   0          2m43s   10.244.0.6   minikube       &lt;none&gt;           &lt;none&gt;<br>zk-1   1/1     Running   0          2m23s   10.244.3.6   minikube-m04   &lt;none&gt;           &lt;none&gt;<br>zk-2   1/1     Running   0          2m1s    10.244.1.5   minikube-m02   &lt;none&gt;           &lt;none&gt;</pre><h4>ARCUS cluster 구성</h4><p>ZooKeeper ensemble이 정상 동작하는 상태에서, 다음과 같은 yaml 파일을 적용하여 현재 namespace에 3개 노드로 구성된 ARCUS 캐시 클러스터를 구성할 수 있습니다. 아래 파일은 default namespace를 기준으로 작성되었으며, 다른 namespace를 사용하는 경우 zk conn str과 server fqdn의 default.svc 부분을 &lt;namespace&gt;.svc와 같이 변경해 주어야 합니다.</p><pre>apiVersion: v1<br>kind: Service<br>metadata:<br>  name: arcus-mc<br>  labels:<br>    app: arcus-memcached<br>    service-code: test<br>spec:<br>  ports:<br>  - port: 11211<br>    name: arcus-mc<br>  selector:<br>    app: arcus-memcached<br>    service-code: test<br>---<br>apiVersion: apps/v1<br>kind: StatefulSet<br>metadata:<br>  name: arcus-mc<br>spec:<br>  selector:<br>    matchLabels:<br>      app: arcus-memcached<br>      service-code: test<br>  serviceName: arcus-mc<br>  replicas: 3<br>  template:<br>    metadata:<br>      name: arcus-memcached<br>      labels:<br>        app: arcus-memcached<br>        service-code: test<br>    spec:<br>      affinity:<br>        podAntiAffinity:<br>          preferredDuringSchedulingIgnoredDuringExecution:<br>          - weight: 100<br>            podAffinityTerm:<br>              labelSelector:<br>                matchExpressions:<br>                - key: &quot;service-code&quot;<br>                  operator: In<br>                  values:<br>                  - test<br>              topologyKey: &quot;kubernetes.io/hostname&quot;<br>      containers:<br>      - name: memcached<br>        image: jam2in/arcus-memcached:1.13.5<br>        args:<br>        - &quot;-v&quot;<br>        - &quot;-m&quot;<br>        - &quot;100&quot;<br>        - &quot;-z&quot;<br>        - &quot;zk-cs.default.svc.cluster.local:2181&quot; # zk conn str<br>      initContainers:<br>      - name: arcus-tool<br>        image: jam2in/zkcli:python<br>        args:<br>          - &quot;arcus.memcached&quot;<br>          - &quot;add&quot;<br>          - &quot;zk-cs.default.svc.cluster.local:2181&quot; # zk conn str<br>          - &quot;$(POD_NAME).mc.default.svc.cluster.local:11211&quot; # server fqdn<br>          - &quot;test&quot; # service-code<br>        env:<br>        - name: POD_NAME<br>          valueFrom:<br>            fieldRef:<br>              fieldPath: metadata.name</pre><p>주요 설정은 다음과 같습니다.</p><ul><li>replicas: 구동하려는 Pod 수 입니다.</li><li>podAntiAffinity: 각 Pod가 가능한 서로 다른 장비에 배치되도록 합니다.</li><li>initContainer: arcus-memcached가 실행되기 전에 ZooKeeper에 Znode를 생성하는 작업을 수행합니다.</li></ul><blockquote>ZooKeeper 프로세스를 PM 또는 VM 환경에 직접 구동하고 ARCUS 캐시 서버만 Kubernetes 환경에서 동작시키는 형태의 구성도 가능합니다. 이렇게 구성하려면 yaml 내용 중 zk conn str 부분에 해당 ZooKeeper 주소를 입력하면 됩니다.</blockquote><p>이렇게 yaml 파일을 작성한 뒤에는 kubectl apply 명령으로 현재 Kubernetes context에 적용할 수 있습니다.</p><pre>$ kubectl apply -f &lt;arcus-memcached.yaml&gt;<br>service/mc created<br>statefulset.apps/mc created</pre><p>ZooKeeper와 마찬가지로, 각 Pod가 모두 구동되고 나면 kubectl get pods 명령으로 상태를 확인할 수 있습니다.</p><pre>$ kubectl get pods -o wide<br>NAME   READY   STATUS    RESTARTS   AGE     IP           NODE           NOMINATED NODE   READINESS GATES<br>mc-0   1/1     Running   0          2m36s   10.244.2.5   minikube-m03   &lt;none&gt;           &lt;none&gt;<br>mc-1   1/1     Running   0          2m33s   10.244.1.6   minikube-m02   &lt;none&gt;           &lt;none&gt;<br>mc-2   1/1     Running   0          69s     10.244.3.7   minikube-m04   &lt;none&gt;           &lt;none&gt;<br>zk-0   1/1     Running   0          23m     10.244.0.6   minikube       &lt;none&gt;           &lt;none&gt;<br>zk-1   1/1     Running   0          23m     10.244.3.6   minikube-m04   &lt;none&gt;           &lt;none&gt;<br>zk-2   1/1     Running   0          23m     10.244.1.5   minikube-m02   &lt;none&gt;           &lt;none&gt;</pre><h4>busybox</h4><p>이제 총 3개 Pod로 구성된 ARCUS 캐시 클러스터가 구동되었습니다. 이번에는 각 프로세스가 잘 동작하고 있는지 확인해 보겠습니다. Kubernetes 환경에서 실행 중인 서비스 디버깅을 위해서 busybox를 사용할 수 있습니다. 자세한 내용은 <a href="https://kubernetes.io/docs/tasks/debug/debug-application/debug-service/">Debug Services</a> 문서를 참고 바랍니다.</p><pre>kubectl run -it --rm --restart=Never busybox --image=gcr.io/google-containers/busybox sh</pre><p>busybox 컨테이너에서 nc, telnet등의 명령을 통해 각 프로세스에 명령을 전송하고 응답을 확인할 수 있습니다. 아래 예시에서 IP 주소 대신 Pod의 FQDN을 사용해도 됩니다.</p><pre>/ # echo srvr | nc 10.244.0.6 2181<br>Zookeeper version: 3.4.10-39d3a4f269333c922ed3db283be479f9deacaa0f, built on 03/23/2017 10:13 GMT<br>Latency min/avg/max: 0/0/39<br>Received: 150651<br>Sent: 150650<br>Connections: 2<br>Outstanding: 0<br>Zxid: 0x200000025<br>Mode: follower<br>Node count: 23<br><br>/ # echo version | nc 10.244.2.5 11211<br>VERSION 1.13.5</pre><h4>마치며</h4><p>지금까지 쿠버네티스 환경에서 ARCUS 캐시 클러스터를 구동하는 방법에 대해 간단히 알아보았습니다. 쿠버네티스는 컨테이너를 활용한 배포 시 사실상 표준(de facto)으로 사용되고 있습니다. 본 포스팅에서 안내드린 내용만으로도 기본적인 클러스터 구성 및 사용이 가능하지만, ARCUS 서버 재구동 없이 설정을 변경하거나 다양한 이벤트 발생 시 유연한 처리가 어렵습니다. ARCUS Enterprise Edition을 구독한 고객에게는 최상의 안정성과 고가용성을 보장하기 위해 ARCUS Operator를 제공하고 있습니다. 본 포스트 또는 ARCUS Operator에 대한 문의 사항이 있다면 contact@jam2in.com 으로 연락 바랍니다.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=50ae837fd966" width="1" height="1" alt=""><hr><p><a href="https://medium.com/jam2in/kubernetes-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-arcus-%EC%BA%90%EC%8B%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0-50ae837fd966">Kubernetes 환경에서 ARCUS 캐시 사용하기</a> was originally published in <a href="https://medium.com/jam2in">JaM2in</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Docker 컨테이너 환경에서 ARCUS 캐시 사용하기]]></title>
            <link>https://medium.com/jam2in/docker-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-arcus-%EC%BA%90%EC%8B%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0-aa9dce08210c?source=rss----c55eb3541815---4</link>
            <guid isPermaLink="false">https://medium.com/p/aa9dce08210c</guid>
            <category><![CDATA[docker]]></category>
            <category><![CDATA[arcus]]></category>
            <dc:creator><![CDATA[Imoliviarla]]></dc:creator>
            <pubDate>Thu, 29 Feb 2024 11:46:09 GMT</pubDate>
            <atom:updated>2024-03-04T01:56:40.354Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Chg0OMZW1-pnrLfvMI2ftg.png" /></figure><p>ARCUS 캐시를 직접 장비에 설치하여 사용하기 위해서는 의존성을 설치하고 컴파일한 후 실행해야 하는 복잡함이 존재합니다. 이번 포스팅에서는 ARCUS를 간편하게 Docker 환경에서 사용하는 방법을 살펴봅니다. Docker에 대한 개념을 먼저 간단히 설명 드리고, 단일 캐시 노드를 구동하는 방법부터 Docker Compose를 활용해 캐시 클러스터를 구동하는 방법까지 차근차근 나아가 보겠습니다.</p><h3>Docker란?</h3><p>Docker는 컨테이너 기술을 이용해 리눅스 프로세스를 격리된 환경에서 실행하는 가상화 플랫폼입니다. 여러 개의 프로세스를 동일한 환경에서 실행할 수 있다는 점에서 가상머신과 비슷하지만, Guest OS를 요구하지 않아 가상머신보다 성능 상 이점이 큽니다. 또한 Docker Hub를 통해 이미지를 공유하면 누구나 접근할 수 있고, Docker가 설치되어 있다면 바로 로컬 환경에서 사용해 볼 수 있습니다. Docker Compose 기능을 활용하면 여러 Docker 컨테이너를 하나의 어플리케이션으로 동작할 수 있도록 묶어 한 번에 띄울 수 있기 때문에 클러스터링에 용이합니다.</p><p>이번 포스팅에서는 Docker Hub에서 잼투인이 제공하는 arcus-memcached 이미지를 사용해 ARCUS 캐시 서버를 간편하게 구동하고 동작을 확인해보겠습니다. 그리고 Docker Compose를 사용하여 캐시 서버 3개로 구성된 ARCUS 캐시 클러스터를 구동해 보겠습니다.</p><h3>단일 ARCUS 캐시 서버 실행</h3><p>먼저 arcus-memcached 이미지를 사용해 하나의 ARCUS 캐시 서버를 실행해보겠습니다. 사용한 Docker 버전은 24.0.5 입니다.</p><p><strong>1) Docker 이미지 가져오기</strong></p><p>docker pull명령어를 사용하면 Docker Hub로부터 arcus-memcached 이미지를 pull 할 수 있습니다. 참고로 Docker 이미지를 가져오지 않은 상태여도 다음 단계의 컨테이너 실행 명령어를 수행할 때 자동으로 pull을 하기 때문에 이 과정은 생략해도 됩니다.</p><pre>docker pull jam2in/arcus-memcached</pre><p><strong>2) Docker container 실행하기</strong></p><p>docker run 명령어를 이용해 arcus-memcached 이미지를 기반으로 컨테이너를 생성하고 실행합니다. 아래 명령어를 실행하면 11211번 포트를 listen하는 ARCUS 캐시 서버 한 개가 컨테이너 상에서 실행됩니다.</p><pre>docker run --name arcus_memcached -p 11211:11211 -d jam2in/arcus-memcached</pre><p>컨테이너를 실행할 때 docker run 명령의 실행 옵션과 ARCUS 캐시 구동 옵션을 사용할 수 있습니다. docker run 명령에 사용된 옵션을 간단히 설명드리겠습니다.</p><ul><li>name: 실행할 컨테이너의 이름을 지정합니다. 값을 주지 않을 경우 무작위로 이름이 부여됩니다.</li><li>p: host와 컨테이너의 port를 매핑하는 옵션입니다. host-port:container-port 형식으로 작성합니다.</li><li>d: 옵션을 부여하면 백그라운드에서 실행합니다.</li><li>자세한 docker run 옵션은 <a href="https://docs.docker.com/engine/reference/commandline/run/">링크</a>를 참고 바랍니다.</li></ul><p>ARCUS 캐시의 구동 옵션은 아래와 같이 이미지 이름 다음에 지정해야 하며, 별도로 지정하지 않으면 기본값이 사용됩니다.</p><pre>docker run --name arcus_memcached -p 11212:11212 -d jam2in/arcus-memcached -p 11212 -m 100 -c 100 -v</pre><p>ARCUS 캐시의 구동 옵션을 간단히 설명드리겠습니다.</p><ul><li>p: listen할 port를 지정합니다. 이 옵션을 지정한 후, 앞서 docker run 옵션에서 명시한 container port도 동일하게 설정해주어야 합니다.</li><li>m: 캐시에서 사용할 최대 메모리(MB)를 지정합니다.</li><li>c: 캐시 서버에 연결 가능한 최대 커넥션 개수를 지정할 수 있습니다.</li><li>v: 캐시 서버 로그를 확인하기 위한 옵션입니다. 이 옵션을 지정하지 않으면 WARN 레벨의 로그만 볼 수 있으며, -v 지정 시 INFO 레벨의 로그를, -vv 지정 시 DEBUG 레벨의 로그를, -vvv 지정 시 DETAIL 레벨의 로그를 확인할 수 있습니다.</li><li>z: 캐시 클러스터로 구동 시 필요한 옵션으로, 캐시 서버에서 연결할 zookeeper 앙상블의 주소를 입력합니다.</li><li>자세한 ARCUS 캐시 구동 옵션 설명과 기본값은 <a href="https://github.com/naver/arcus-memcached/wiki/memcached-%EA%B5%AC%EB%8F%99-%EC%98%B5%EC%85%98">링크</a>를 참고바랍니다.</li></ul><p><strong>3) ARCUS 동작 확인</strong></p><p>ARCUS 캐시 서버를 컨테이너를 실행한 후에는 nc 명령어를 이용해 정상적으로 구동되었는지 확인할 수 있습니다.</p><pre>echo stats | nc localhost 11211</pre><p>컨테이너가 정상적으로 실행되었다면 다음과 같은 응답을 받을 수 있습니다.</p><pre>STAT pid 1<br>STAT uptime 606<br>STAT time 1701941770<br>STAT version 1.13.4<br>STAT libevent 2.1.12-stable<br>STAT pointer_size 64<br>STAT hb_count 201<br>STAT hb_latency 339<br>....</pre><blockquote>Mac Sonoma OS 등 nc 명령어가 동작하지 않는 환경이 있다면, telnet 명령어를 사용해 확인해주세요.</blockquote><p><strong>4) ARCUS 캐시 중지</strong></p><p>구동중인 ARCUS 캐시 서버를 중지하고자 한다면, 아래와 같이 docker stop container-name명령어를 사용해 컨테이너를 중지해야 합니다.</p><pre>docker stop arcus_memcached</pre><h3>ARCUS 캐시 클러스터 구성 및 실행</h3><p>ARCUS는 ZooKeeper를 이용한 클러스터링 방식을 지원합니다. 이번에는 아래와 같이 ZooKeeper 서버 3개로 구성된 앙상블과 ARCUS 캐시 서버 3개로 구성된 클러스터를 Docker Compose를 이용하여 단일 장비에서 구동해보겠습니다.</p><pre>service code: test<br>+--------------------+   +--------------------+   +--------------------+<br>| zoo1               |   | zoo2               |   | zoo3               |<br>+--------------------+   +--------------------+   +--------------------+<br>|                    |   |                    |   |                    |<br>|container-port:2181 |   |container-port:2181 |   |container-port:2181 |<br>|host-port:2181      |   |host-port:2182      |   |host-port:2183      |<br>|                    |   |                    |   |                    |<br>+--------------------+   +--------------------+   +--------------------+<br>+--------------------+   +--------------------+   +--------------------+<br>| cache1             |   | cache2             |   | cache3             |<br>+--------------------+   +--------------------+   +--------------------+<br>|                    |   |                    |   |                    |<br>|container-port:11211|   |container-port:11211|   |container-port:11211|<br>|host-port:11211     |   |host-port:11212     |   |host-port:11213     |<br>|                    |   |                    |   |                    |<br>+--------------------+   +--------------------+   +--------------------+</pre><p>1) docker-compose.yml 파일 작성</p><p>아래와 같은 과정을 거쳐 Docker Compose로 ARCUS 캐시 클러스터를 구동할 수 있습니다.</p><ul><li>3개의 host로 구성된 Zookeeper 컨테이너를 띄워 앙상블을 구성합니다.</li><li>ZkCli 컨테이너를 구동하여 ARCUS 캐시 서버가 Zookeeper에 등록될 수 있도록 ZNode를 생성합니다. 생성이 완료되면 컨테이너는 종료됩니다.</li><li>3개의 ARCUS 캐시 컨테이너를 띄워 캐시 서버가 클러스터 형태로 실행되도록 합니다.</li></ul><p>다음은 위 컨테이너들을 구동하는 docker-compose.yml 파일입니다.</p><pre>version: &quot;3.1&quot;<br><br>services:<br>  zoo1:<br>    image: zookeeper:3.5.9<br>    hostname: zoo1<br>    ports:<br>    - 2181:2181<br>    environment:<br>      ZOO_MY_ID: 1<br>      ZOO_SERVERS: server.1=zoo1:2888:3888;2181 server.2=zoo2:2888:3888;2181 server.3=zoo3:2888:3888;2181<br>    restart: always<br><br>  zoo2:<br>    image: zookeeper:3.5.9<br>    hostname: zoo2<br>    ports:<br>    - 2182:2181<br>    environment:<br>      ZOO_MY_ID: 2<br>      ZOO_SERVERS: server.1=zoo1:2888:3888;2181 server.2=zoo2:2888:3888;2181 server.3=zoo3:2888:3888;2181<br>    restart: always<br><br>  zoo3:<br>    image: zookeeper:3.5.9<br>    hostname: zoo3<br>    ports:<br>    - 2183:2181<br>    environment:<br>      ZOO_MY_ID: 3<br>      ZOO_SERVERS: server.1=zoo1:2888:3888;2181 server.2=zoo2:2888:3888;2181 server.3=zoo3:2888:3888;2181<br>    restart: always<br><br>  register:<br>    depends_on:<br>    - zoo1<br>    - zoo2<br>    - zoo3<br>    image: jam2in/zkcli:3.5.9<br>    environment:<br>      ZK_ENSEMBLE: zoo1:2181,zoo2:2181,zoo3:2181<br>      SERVICE_CODE: test<br>      CACHENODES: cache1:11211,cache2:11212,cache3:11213<br>    restart: on-failure<br><br>  cache1:<br>    depends_on:<br>      register:<br>        condition: service_completed_successfully<br>    image: jam2in/arcus-memcached<br>    command: -m 100 -p 11211 -z zoo1:2181,zoo2:2181,zoo3:2181<br>    hostname: cache1<br>    ports:<br>    - 11211:11211<br>    environment:<br>      ARCUS_CACHE_PUBLIC_IP: 127.0.0.1<br>    restart: always<br><br>  cache2:<br>    depends_on:<br>      register:<br>        condition: service_completed_successfully<br>    image: jam2in/arcus-memcached<br>    command: -m 100 -p 11212 -z zoo1:2181,zoo2:2181,zoo3:2181<br>    hostname: cache2<br>    ports:<br>    - 11212:11212<br>    environment:<br>      ARCUS_CACHE_PUBLIC_IP: 127.0.0.1<br>    restart: always<br><br>  cache3:<br>    depends_on:<br>      register:<br>        condition: service_completed_successfully<br>    image: jam2in/arcus-memcached<br>    command: -m 100 -p 11213 -z zoo1:2181,zoo2:2181,zoo3:2181<br>    hostname: cache3<br>    ports:<br>    - 11213:11213<br>    environment:<br>      ARCUS_CACHE_PUBLIC_IP: 127.0.0.1<br>    restart: always</pre><p>yml 파일에 작성한 옵션들 중 중요한 옵션만 간단히 살펴보겠습니다. 먼저 ZooKeeper 컨테이너 구동 시 사용한 옵션은 아래와 같습니다.</p><ul><li>ZOO_MY_ID: ZooKeeper 서버의 myid 값을 지정합니다. 각 컨테이너마다 다르게 설정해주어야 하나의 앙상블을 구성할 수 있습니다. ID는 앙상블 내에서 고유해야 하며 1에서 255 사이의 값을 가져야 합니다.</li><li>ZOO_SERVERS: ZooKeeper 앙상블 구성을 위한 ZooKeeper 주소 목록을 server.myid=host:server-port:election-port;client-port 형태로 입력합니다.</li></ul><p>다음으로 zkcli 컨테이너 구동 시 사용한 옵션은 아래와 같습니다. 참고로 zkcli 컨테이너는 ZooKeeper 앙상블에 접속하여 캐시 클러스터 정보를 ZNode로 저장하는 역할을 합니다. 이 정보는 클러스터 운영 시 사용됩니다.</p><ul><li>depends_on: ZooKeeper 컨테이너들이 모두 구동되어 앙상블을 형성한 후에 zkcli 컨테이너가 시작되도록 합니다.</li><li>ZK_ENSEMBLE: 구성된 ZooKeeper 앙상블 주소를 입력하여 zkcli 컨테이너가 ZNode를 생성할 수 있도록 합니다.</li><li>SERVICE_CODE: ARCUS 캐시 클러스터의 서비스 코드를 지정합니다.</li><li>CACHENODES: ARCUS 캐시 클러스터의 모든 캐시 서버 주소를 입력하여 ZooKeeper 앙상블에서 관리되도록 합니다.</li></ul><p>마지막으로 ARCUS 캐시 컨테이너 구동 시 사용한 옵션은 아래와 같습니다.</p><ul><li>command: 단일 캐시 서버 구동 부분에서 설명드린 캐시 구동 옵션을 지정합니다. 여기서는 memory는 최대 100MB 사용하도록 하고, port는 각 서버마다 11211부터 11213까지 사용하고, -z 옵션으로 zookeeper 앙상블과 연결을 맺도록 했습니다.</li><li>ARCUS_CACHE_PUBLIC_IP: 도커 컨테이너 내부가 아닌 외부 환경에서 ZooKeeper 앙상블로부터 ARCUS 캐시 서버에 접근할 수 있도록 IP를 입력해줍니다.</li></ul><blockquote>기존에 다른 프로세스가 2181~2183 포트와 11211~11213 포트를 사용하고 있지 않는지 확인하여 만약 해당 포트를 사용중인 프로세스가 존재한다면 ZooKeeper 앙상블이나 캐시 서버가 다른 포트를 사용하도록 설정 파일을 수정해주세요.</blockquote><p>2) docker compose 실행</p><p>docker-compose.yml 파일이 저장된 경로에서 다음 명령어를 실행하면 ZooKeeper 앙상블과 캐시 서버가 백그라운드로 실행됩니다.</p><pre>docker compose up -d</pre><p>3) ARCUS 동작 확인</p><p>Zookeeper 서버에 srvr 명령을 보내 서버가 정상적으로 실행되었는지 확인할 수 있습니다.</p><pre>echo srvr | nc localhost 2181<br>echo srvr | nc localhost 2182<br>echo srvr | nc localhost 2183</pre><p>마찬가지로 ARCUS 캐시 서버에 stats 명령을 보내 정상 실행 여부를 확인할 수 있습니다.</p><pre>echo stats | nc localhost 11211<br>echo stats | nc localhost 11212<br>echo stats | nc localhost 11213</pre><p>이외에도 telnet 명령어를 통해 ARCUS 캐시와 연결해 다양한 ARCUS 캐시 명령어를 사용해 볼 수 있습니다.</p><h3>마치며</h3><p>지금까지 Docker를 사용한 ARCUS 캐시 서버 및 캐시 클러스터 구동 방법에 대해 간단히 알아보았습니다. 컨테이너 기술은 쿠버네티스나 서버리스 기반 서비스를 개발하는데 근간이 되는 기술로, 수많은 서비스들을 빠른 시간내에 배포하는 작업이 빈번히 요구되는 운영 환경 조건들을 충족해줄 수 있어 업계에서 굉장히 많이 사용되고 있습니다.</p><p>이번 포스팅에서 다룬 Docker와 Docker Compose는 한 개의 호스트에서만 ARCUS 캐시 클러스터를 구동할 수 있기 때문에 실서비스에 적용하기 어렵다는 단점이 존재합니다. 다음 포스팅에서는 여러 호스트에서 컨테이너들을 효과적으로 관리할 수 있는 쿠버네티스를 이용해 ARCUS 캐시 클러스터를 구동하는 방법을 소개하겠습니다.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=aa9dce08210c" width="1" height="1" alt=""><hr><p><a href="https://medium.com/jam2in/docker-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-arcus-%EC%BA%90%EC%8B%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0-aa9dce08210c">Docker 컨테이너 환경에서 ARCUS 캐시 사용하기</a> was originally published in <a href="https://medium.com/jam2in">JaM2in</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Arcus 캐시에서 MaxBkeyRange 이용하여 최근 내역 자동 관리 방안]]></title>
            <link>https://medium.com/jam2in/arcus-%EC%BA%90%EC%8B%9C%EC%97%90%EC%84%9C-maxbkeyrange-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-%EC%B5%9C%EA%B7%BC-%EB%82%B4%EC%97%AD%EC%9D%84-%EC%9E%90%EB%8F%99%EC%9C%BC%EB%A1%9C-%EA%B4%80%EB%A6%AC-%EB%B0%A9%EC%95%88-da8235f8d48b?source=rss----c55eb3541815---4</link>
            <guid isPermaLink="false">https://medium.com/p/da8235f8d48b</guid>
            <category><![CDATA[arcus-cache-cluster]]></category>
            <category><![CDATA[cache]]></category>
            <category><![CDATA[java]]></category>
            <category><![CDATA[b-tree]]></category>
            <category><![CDATA[caching]]></category>
            <dc:creator><![CDATA[moonseop kim]]></dc:creator>
            <pubDate>Fri, 25 Feb 2022 08:03:18 GMT</pubDate>
            <atom:updated>2022-02-25T11:01:11.316Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*17SQ-T_Jt--f03TQJHZ9Hw.png" /></figure><p>SNS 혹은 쇼핑몰에서 사용자들에게 편리한 서비스 제공을 위해 사용자의 최근 내역(활동 내역, 조회한 상품 내역, 장바구니)들을 저장하여 제공하고 있습니다. 저장된 데이터는 영구적으로 저장하지 않고 최근 N일 내의 데이터만을 유지하며 사용자에게 제공하는 것이 일반적입니다.</p><p>대부분의 최근 내역은 DB에 저장하여 관리 합니다. 최근 내역의 관리를 위해서 주기적으로 스케줄링 작업을 통해 오래된 내역을 삭제해야 합니다. 뿐만 아니라 최근 내역은 사용자가 빈번하게 요청하는 데이터이기 때문에 반복적으로 DB 조회가 요청될 수 있고, 데이터 제공 시에 상품 등과 join 질의가 수행된다면 DB에 더 큰 부담이 됩니다. 이러한 스케줄링 작업 및 빈번한 조회 요청으로 부터 DB 부하를 줄이기 위해 최근 내역 데이터를 캐시에 저장해 제공할 수 있습니다.</p><p>하지만, 캐시에서도 최근 내역 데이터를 저장하여 관리하는 것은 복잡한 작업입니다. 예를 들어 TreeMap형태로 사용자의 내역 데이터를 저장한다고 가정하면, 오래된 내역을 제거하기 위해 아래와 같은 작업을 주기적으로 수행해야 합니다.</p><ul><li>현재일자와 비교하여 제거할 내역의 시간 범위 계산</li><li>계산된 범위에 속한 오래된 내역 일괄 제거</li></ul><p>주기적으로 DB에서 사용자의 전체 목록을 조회하여 캐시에 저장된 사용자들의 오래된 내역 데이터를 삭제하는 것은 DB뿐만 아니라 캐시 성능에도 영향을 미치어 캐시 사용 효율이 떨어지게 됩니다.</p><p>만약 이런 최근 내역 데이터를 캐시에서 설정한 일자로부터 경과한 데이터에 대해 스케줄 작업없이 자동으로 삭제해 준다면, 시스템 전체 성능 향상 뿐만아니라 응용 개발자의 입장에서 얼마나 편리할까요?</p><p>지금부터 이 모든 기능이 수행 가능한 Arcus의 기능을 소개하도록 하겠습니다.</p><p>Arcus에서 지원하는 아이템 유형은 key-value 유형과 list, set, map, b+tree 형태의 collection 유형이 있으며, 본 글에서는 b+tree 유형의 속성 정보 중 하나인 maxbkeyrange에 대해서 설명하고 사용 예시를 통해 최근 내역 데이터를 관리하는 방법을 소개하도록 하겠습니다.</p><h3>B+tree</h3><p>b+tree는 Arcus에서 지원하는 Collection 유형 중 하나로, leaf 노드에 &lt;bkey, data&gt;구조의 elements를 정렬하여 저장하는 자료구조를 가지며, 아래 그림과 같습니다.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/533/1*yBKNpyqvflN3cKojXRqaaA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*aw3cfZeelCU19eS3CSgegQ.png" /></figure><h3>MaxBkeyRange</h3><p>maxbkeyrange는 b+tree 유형에만 제공되는 b+tree only 속성 정보이며, Max(최대) Bkey(bkey) Range(범위) 말 그대로 bkey들의 최대 범위를 지정하는 속성 정보입니다.</p><p>더욱 자세하게 설명하면, maxbkeyrange는 b+tree내에 저장할 수 있는 제일 작은 bkey(smallest bkey)와 제일 큰 (largest bkey)의 사이의 최대 범위를 나타냅니다. b+tree에 maxbkeyrange를 설정하고 새로운 element를 추가할 때 maxbkeyrange 범위를 벗어나면 b+tree의 overflowaction 정책에 의해 기존 element가 제거되거나(smallest_trim) 새로운 element가 추가되지 않게(largest_trim) 됩니다.</p><p>아래 그림은 maxbkeyrange가 10인 b+tree에 bkey가 11인 새로운 element가 삽입된다고 했을 때의 모습입니다. maxbkeyrange 조건을 위배하지 않았으므로, 새로운 element가 정상적으로 삽입됩니다.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/612/1*xGXjDGGSNabOIjTdt9MDdQ.png" /></figure><p>다음으로 bkey가 12인 element를 삽입합니다. 맨 앞 element의 bkey값은 1이고 추가되는 element의 bkey값은 12로 둘의 차이는 11이 되며 maxbkeyrange의 범위를 초과하게 됩니다. 이 경우, maxbkeyrange 설정 준수를 위해 현재 b+tree의 overflowaction 정책을 수행하게 됩니다.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/678/1*UH60DhfOWgcemPiRrB7-dQ.png" /></figure><p>현재 b+tree의 overflowaction 정책이 “smallest_trim”(최소 bkey를 가진 element 삭제)이라고 한다면, bkey가 1인 맨 앞의 element는 삭제되고 새로 추가되는 element가 b+tree에 삽입되게 됩니다. overflowaction 정책이 수행되고 새로운 element 삽입에 관한 결과는 아래의 그림과 같습니다.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/519/1*0M2QrR85NaZH4KwFYN7ykw.png" /></figure><h4>코드로 살펴보기</h4><p>다음은 위의 예시를 arcus-java-client 이용하여 응용 코드로 살펴 보겠습니다. maxbkeyrange를 10으로 overflowaction은 smallest_trim으로 설정한 뒤 15개의 elements를 삽입 했을 때 수행 결과를 살펴 볼 수 있습니다.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ac3dc0971e443224833f29aed4e6b450/href">https://medium.com/media/ac3dc0971e443224833f29aed4e6b450/href</a></iframe><p>실행 결과, 맨 앞 element의 bkey와 맨 뒤 element의 bkey의 차이가 10이하(bkey 1 ~ bkey 11) 인 경우에는 element의 삭제가 일어나지 않고, 모든 element의 삽입이 이루어졌으며, 10을 초과시(bkey 1 ~ bkey 12) overflowaction 정책에 따라 맨 앞 element가 삭제되고 새로운 element가 삽입됨을 볼 수 있습니다.</p><pre>b+tree 생성결과 :CREATED<br>1번째 element 삽입 결과 :STORED<br>맨 앞 element bkey : 1, 맨 뒤 element bkey : 1<br>2번째 element 삽입 결과 :STORED<br>맨 앞 element bkey : 1, 맨 뒤 element bkey : 2<br>3번째 element 삽입 결과 :STORED<br>맨 앞 element bkey : 1, 맨 뒤 element bkey : 3<br>4번째 element 삽입 결과 :STORED<br>맨 앞 element bkey : 1, 맨 뒤 element bkey : 4<br>5번째 element 삽입 결과 :STORED<br>맨 앞 element bkey : 1, 맨 뒤 element bkey : 5<br>6번째 element 삽입 결과 :STORED<br>맨 앞 element bkey : 1, 맨 뒤 element bkey : 6<br>7번째 element 삽입 결과 :STORED<br>맨 앞 element bkey : 1, 맨 뒤 element bkey : 7<br>8번째 element 삽입 결과 :STORED<br>맨 앞 element bkey : 1, 맨 뒤 element bkey : 8<br>9번째 element 삽입 결과 :STORED<br>맨 앞 element bkey : 1, 맨 뒤 element bkey : 9<br>10번째 element 삽입 결과 :STORED<br>맨 앞 element bkey : 1, 맨 뒤 element bkey : 10<br>11번째 element 삽입 결과 :STORED<br>맨 앞 element bkey : 1, 맨 뒤 element bkey : 11<br>12번째 element 삽입 결과 :STORED<br>맨 앞 element bkey : 2, 맨 뒤 element bkey : 12<br>13번째 element 삽입 결과 :STORED<br>맨 앞 element bkey : 3, 맨 뒤 element bkey : 13<br>14번째 element 삽입 결과 :STORED<br>맨 앞 element bkey : 4, 맨 뒤 element bkey : 14<br>15번째 element 삽입 결과 :STORED<br>맨 앞 element bkey : 5, 맨 뒤 element bkey : 15</pre><pre>attribute 정보<br>type=btree<br>expiretime=10<br>count=11<br>overflowaction=smallest_trim<br>maxbkeyrange=10<br>minbkey=5<br>maxbkey=15</pre><p>지금까지 가장 작은 bkey와 가장 큰 bkey의 범위를 설정하고 그 범위가 초과하면, b+tree의 smallest_trim overflowaction 정책에 따라 동작이 수행되는 maxbkeyrange 속성에 대해 알아보고 해당 특성을 코드로 살펴보았습니다.</p><h3>MaxBkeyRange 활용</h3><p>다음으로는 maxbkeyrange를 활용하여 응용에서 적용해 볼 수 있는 예제에 대해서 알아보도록 하겠습니다.</p><p>이번 예제에서는 어떤 응용이 b+tree에 데이터를 캐싱하고 최근 5일 치의 데이터만을 유지한다고 가정합니다. 초 단위의 시간 값을 bkey로 사용한다면 maxbkeyrange는 5일 치에 해당하는 값인 432000(5 * 24 * 60 * 60)으로 지정합니다. 그리고 최근 값만을 유지하기 위해 overflowaction 정책을 “smallest_trim”으로 설정합니다. 따라서, 새로운 아이템이 추가될 시에 가장 오래된 아이템과 새로 추가된 아이템이 5일 차이가 난다면 가장 오래된 아이템을 캐시에서 자동으로 삭제하게 됩니다.</p><h4>실제 응용 코드 구현</h4><p>맨 처음 말씀드렸던 SNS 혹은 쇼핑몰에서 나의 최근 내역(활동 내역, 조회한 상품 내역, 장바구니)를 시간순으로 캐싱하는 요구사항이 있습니다. 이 데이터는 영구적으로 캐싱 되지 않고 최근 데이터를 기준으로 5일만 캐싱 된다고 합니다. 해당 요구 사항을 위 예제 설명을 이용해 코드로 구현해 보겠습니다.</p><p>코드 구현은 java를 이용하여 구현하도록 하겠습니다. 먼저 해당 요구사항에 맞게 코드를 구현할 MyService 클래스를 생성해 줍니다.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/5e8f4a0bdc4c5714e41d3df8c7160e22/href">https://medium.com/media/5e8f4a0bdc4c5714e41d3df8c7160e22/href</a></iframe><p>MyService를 수행할 Test class를 생성하거나, Spring의 경우에는 Controller Layer를 통해 각 기능(생성 / 삽입 / 조회)별로 API 요청을 받아 해당 예제를 수행할 수 있습니다.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/27888a3d230be4935c8e4a2ecdf2e445/href">https://medium.com/media/27888a3d230be4935c8e4a2ecdf2e445/href</a></iframe><p>코드를 실행한 결과는 아래와 같습니다. 최근 내역부터 데이터가 출력됩니다.</p><pre>1일차 데이터<br>testValue1<br>2일차 데이터<br>testValue2<br>testValue1<br>3일차 데이터<br>testValue3<br>testValue2<br>testValue1<br>4일차 데이터<br>testValue4<br>testValue3<br>testValue2<br>testValue1<br>5일차 데이터<br>testValue5<br>testValue4<br>testValue3<br>testValue2<br>testValue1<br>6일차 데이터<br>testValue6<br>testValue5<br>testValue4<br>testValue3<br>testValue2<br>testValue1<br>7일차 데이터<br>testValue7<br>testValue6<br>testValue5<br>testValue4<br>testValue3<br>testValue2<br>8일차 데이터<br>testValue8<br>testValue7<br>testValue6<br>testValue5<br>testValue4<br>testValue3<br>9일차 데이터<br>testValue9<br>testValue8<br>testValue7<br>testValue6<br>testValue5<br>testValue4<br>10일차 데이터<br>testValue10<br>testValue9<br>testValue8<br>testValue7<br>testValue6<br>testValue5</pre><p>결과를 살펴보면, 가장 최근 데이터를 기준으로 5일(예제 코드에서는 5초) 내 데이터를 조회할 수 있습니다.</p><p>사용자가 직접 스케줄링 작업을 통해 유저들의 최근 데이터를 삭제를 수행하지 않고, maxbkeyrange 설정을 통해 캐시에서 자동으로 편리하게 최근 데이터를 유지할 수 있습니다.</p><h3>마무리</h3><p>지금까지 Arcus에서 제공하는 b+tree 유형에 대한 maxbkeyrange 속성을 알아보고 응용 사례까지 코드로 구현해 보았습니다. DB의 부담으로 최근 내역 데이터를 캐시에 저장하여 사용할 때, 최근 내역 데이터를 주기적으로 관리하기 위해서는 관리 대상인 아이템의 key 목록이 필요합니다. 하지만 maxbkeyrange를 설정하여 b+tree에 최근 내역 데이터를 저장하여 관리한다면, 관리 대상 아이템의 key 목록 없이 자동으로 최근 내역을 유지할 수 있습니다. 이는 key 목록 관리 및 스케줄링 구현이 필요한 복잡한 응용 개발의 부담을 줄일 수 있습니다. 그러므로 현재 최근 내역을 사용하거나, 사용 예정이거나 혹은 시스템 부담으로 인해 서비스를 제공하기 부담스러웠던 개발자분들에게 maxbkeyrange를 이용한 최근 내역 기능을 구현해 보실 것을 추천드립니다.</p><p>오늘 설명한 maxbkeyrange는 Arcus에서 제공하는 b+tree의 일부 기능입니다. b+tree 외에도 list, set, map과 같은 collection 유형이 존재하며 이를 다양하게 이용할 수 있습니다. 간단하게 key-value 형태의 저장 / 조회 기능만 이용하여 복잡한 응용 코드를 생산하기보다 Arcus에서 제공하는 고급진 기능들을 통해 간결하게 작성할 수 있습니다.</p><p>앞으로도 블로그를 통해 Arcus에서 제공하는 편리한 기능들을 소개하는 시간을 갖도록 하겠습니다. arcus-java-client의 추가적인 기능에 대해 궁금하신 분은 <a href="https://www.jam2in.com/arcus-docs/#/arcus-clients/java-client/1.13/01-arcus-cloud-basics">arcus-java-client-doc</a>에서 더 많은 기능들을 살펴보실 수 있습니다. 감사합니다.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=da8235f8d48b" width="1" height="1" alt=""><hr><p><a href="https://medium.com/jam2in/arcus-%EC%BA%90%EC%8B%9C%EC%97%90%EC%84%9C-maxbkeyrange-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-%EC%B5%9C%EA%B7%BC-%EB%82%B4%EC%97%AD%EC%9D%84-%EC%9E%90%EB%8F%99%EC%9C%BC%EB%A1%9C-%EA%B4%80%EB%A6%AC-%EB%B0%A9%EC%95%88-da8235f8d48b">Arcus 캐시에서 MaxBkeyRange 이용하여 최근 내역 자동 관리 방안</a> was originally published in <a href="https://medium.com/jam2in">JaM2in</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Sustainable Caching Method in ARCUS and How To Apply It]]></title>
            <link>https://medium.com/jam2in/sustainable-caching-method-in-arcus-and-how-to-apply-it-468454bdd3b3?source=rss----c55eb3541815---4</link>
            <guid isPermaLink="false">https://medium.com/p/468454bdd3b3</guid>
            <category><![CDATA[cache]]></category>
            <category><![CDATA[database]]></category>
            <category><![CDATA[cache-stampede]]></category>
            <category><![CDATA[arcus-cache-cluster]]></category>
            <category><![CDATA[jam2in]]></category>
            <dc:creator><![CDATA[N.M.G.]]></dc:creator>
            <pubDate>Mon, 01 Nov 2021 06:17:59 GMT</pubDate>
            <atom:updated>2021-11-01T06:17:59.068Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/757/1*X__bKAyLhB-KNfC-l6nohw.png" /></figure><p>In large-scale applications for general users, when retrieving data (especially hot data), a high volume of requests towards the DB will result in a high load. Therefore a general method to reduce the load on the DB side and to provide a fast response is storing the frequently retrieved data in the distributed cache of an application.</p><p>When it comes to applying cache to the application, Demand-fill caching pattern is the most commonly used method. When an application requests data, first it will be checked in the cache-store, if data exists in the cache, retrieve data from the cache, otherwise, data will be retrieved from the database, stored into the cache, and after that, it will be returned. Hence a fast response can only be provided if data exists in the cache. Please check the <a href="https://medium.com/jam2in/arcus-common-cache-module-with-basic-pattern-caching-in-java-environment-db88c4bf7585">ARCUS Common Cache Module Use with Basic Pattern Caching in Java Environment</a> for more details on the demand-fill method.</p><p>However, the problem with this method is that when data in the database has been modified, this update won’t be reflected on the data in the cache. Therefore to reduce the data mismatch between DB and cache, <strong>Expire Time</strong> or <strong>Time-To-Live(TTL)</strong> is set. Because of this characteristic following issues may occur.</p><ol><li>The difference in response latency to the request, in the cases of caching and expired caching.</li><li>DB load with a high volume of requests, right after the cache data expire until the data is cached again.</li></ol><p>These are the problems that occur when the cached data has expired and all requests go to the database, especially the second issue is referred to as cache stampede.</p><h3>Cache Stampede</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/400/1*4Jtaapi1ARB6jT2gwM6YcA.gif" /><figcaption>remember lion king?</figcaption></figure><p>A stampede is a situation in which a group of large animals suddenly start running in the same direction in a sudden panicked rush. The same name and concept apply to <strong>a</strong> <strong>cache stampede problem</strong> when a popular cache item expires and is led by multiple requests for the item seeing a cache miss, re-requesting the same item from the database which causes high load and high response latency both at the same time. This can cause the following problems.</p><ul><li>Delay of application response time</li><li>Duplicated writes: when retrieving the expired cache item from DB and caching the same data again and again</li></ul><h3>Cache Stampede Mitigation</h3><p>Multiple approaches have been proposed to mitigate the cache stampede problem, one of the well-known approaches is <a href="https://cseweb.ucsd.edu//~avattani/papers/cache_stampede.pdf">“Optimal Probabilistic Cache Stampede Prevention”</a> which has been published at the International Conference on Very Large Data Bases(VLDB). Here are some of the introduced/proposed methods from that paper.</p><ul><li><strong>External re-computation<br></strong>○ Periodically regenerates cache items in the background process to prevent cache misses.<br>○ <strong>Cons.:</strong> burden of maintenance and periodically regenerated item list (key list) is required</li><li><strong>Locking<br>○ </strong>Upon a cache miss, grant a lock to the first request to prevent duplicated DB retrievals and renew a cache.<strong><br> ○ Cons.: </strong>only duplicated retrievals and duplicated write issues caused by cache miss can be solved this way, but other response latency issues continue when the cache expires.</li><li><strong>Probabilistic Early Expiration (PER)</strong><br><strong>○ </strong>Renew the cache with the calculated probability before the cache data expires using a stochastic algorithm.</li></ul><p>Among the aforementioned methods, the main approach presented in detail in the paper is PER. To simply describe PER, it would be easy to explain it with the following images.</p><ul><li><strong>State of Basic Caching</strong></li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*HnirjNjHsMrkiJZgUBG5sg.png" /></figure><p>The blue section is where the caching is applied, and the white part is where the cache got expired. Since a cache miss occurs only in the white part, it becomes a section where a lot of retrieval requests burden the database.</p><ul><li><strong>State of Caching with PER</strong></li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5JUQVuUdxdjwq90wbTezVA.png" /></figure><p>This is a state in which caching is maintained without expiration by reaching before the cache item expires. Compare to the above-mentioned basic caching the recaching occurs before expiration time thus preventing cache item expiration.</p><p>Every time when a thread that processes a request retrieves the cache data, PER algorithm performs recaching with the random probability compared to the remaining expiration time, and as the expiration time is approaching closer, the probability of recaching also increases. More about the probabilistic algorithm will be discussed later.</p><p>Now, let’s find out through a simple test how the algorithm actually works.</p><h3>Cache Stampede Mitigation: Test</h3><p>Let&#39;s compare the performance outcomes of a cache expiration time for both basic caching and caching with PER by establishing the following test environments.</p><ul><li><strong>Test Conditions</strong></li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/677/1*7kXEmdmLk_uxcn5Eo_VN3A.png" /></figure><ul><li><strong>Jmeter Settings</strong></li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/679/1*ThmwfxK2Ih8y-rKWzmL3FA.png" /></figure><ul><li><strong>Measurement Factors</strong><br><strong>○ </strong>number of times cache miss occurred<br><strong>○ </strong>response latency due to cache stampede<br><strong>○ </strong>number of duplicated writes due to cache stampede</li></ul><p>The results of the test of basic caching that doesn’t have any applied solution and caching with PER solution are as follows.</p><ul><li><strong>Caching</strong> (cache miss: 4-times, response latency: 500ms, duplicated writes: 242-times)</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*q3iz3Cl1tswl0IdYAuEg9A.png" /></figure><ul><li><strong>PER </strong>(cache miss: 0-times, response latency: 100~200ms, duplicated writes: 43-times)</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ANAYvTYrAY-0B0Buss6pHA.png" /></figure><p>Looking at the results, in the basic caching cache miss occurred due to the expiration, following many requests run to DB at the same time, thus resulting in cache stampede. Therefore there was a delay with responses at the DB side. Furthermore, due to the cache miss, all requests made for the cache item caused duplicated DB retrievals and cache writings.</p><p>On the other side, because PER algorithm is applied, recaching has been performed in advance and there was no cache miss in all parts that were scheduled to expire. Only recaching performers sent requests to DB and the response time and the number of duplicated writes were significantly reduced. Consequently, this confirms that PER is effective in preventing cache stampedes.</p><h3>Practical Application Environment Test</h3><p>In practical real applications, most DB retrieval times take from several to tens of milliseconds(<strong><em>ms</em></strong>). In this test, we have set the expiration time of cache items from dozens of seconds ~ to several minutes. Now let’s test how does PER actually works in real-world applications.</p><ul><li><strong>Test conditions</strong></li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/701/1*pAru0NZNCYhvSX8VVVOqgg.png" /></figure><ul><li><strong>Jmeter Settings</strong></li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eyIOA57WtYbU2Vydax9jyg.png" /></figure><ul><li><strong>Measurement Factors</strong><br><strong>○ </strong>number of times cache miss occurred<br><strong>○ </strong>response latency due to cache stampede<br><strong>○ </strong>number of duplicated writes due to cache stampede</li></ul><p>The results of the test, composed for practical applications are as follows.</p><ul><li><strong>100tps</strong> (cache miss: 5-times, response latency: 0~100<em>ms</em>, duplicated writes: 95-times)</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8YEiYhI_22sbtjoDDHVzsg.png" /></figure><ul><li><strong>10tps </strong>(cache miss: 10-times, response latency: 100<em>ms</em>, duplicated writes: 20-times)</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FskkvnFD0k0zYh8pgyecFQ.png" /></figure><p>Obviously, when testing on the actual application environment, the results differ from the initial one.</p><p>Compared to the initial test, unlike before in 100<em>tps</em> where only DB retrieval time has changed, some of them are recached, some had cache misses, and even resulting in cache stampede(response latency and duplicate DB retrievals and cache write) repeatedly. And in the 10<em>tps</em> there was no recaching in all expiration periods, causing a cache miss. Now the question is why did we get different results?</p><h3>Problems in Practical Application Environment Test</h3><ol><li><strong>ARCUS Cache measures expiration time in seconds and does not expire precisely at the actual time.</strong><br><strong>○ </strong>A restriction on the application of the PER algorithm, which determines whether to recache in actual <strong><em>ms units</em></strong>.</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/961/1*KjnfWV-XHzoP93dNr_UDZA.png" /></figure><p>In ARCUS, system calculations for item expiration are performed in seconds. I will use the above image to elaborate on my explanations. If the expiration time of different cache items is set to 3 seconds at different points between 0 and 1 second, then actual expiration will be performed simultaneously. For example, as shown in the picture, if you set expire time to 3 seconds at the point of 0th second, then it will expire at 3rd seconds, if you set expire time to 3 seconds at the point of 0.5th seconds, then it will also expire at 3rd seconds, not 3.5th seconds. The reason is the cache item expires with the ARCUS’s internal timer that works in seconds.</p><p>Even if the application measures the expiration time in detail with <em>ms unit</em>, because ARCUS processes expiration time in <em>seconds</em>, between application and ARCUS there is a gap of <strong><em>milliseconds, </em></strong>which affects the gap when determining whether or not to recache the item. Now that difference has been clarified, we can say that the reason why the repetitive cache misses occurred in the above test of 100<em>tps</em> is due to this problem.</p><p><strong>2. Requests with low tps have a low re-caching probability.</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*l7tTe8jkdRdF5ktbnlN8gw.png" /></figure><p>Cache data with about 100 requests per second in practical applications corresponds to hot data. However, most of the data cached in the application will not/cannot be the hot data. If it’s a rare request several times per second or less, as shown on the right image above, there was no request for determining whether or not to recache, thus resulting in cache miss without recaching. The results can be checked in the graph of 10<em>tps</em> of the above test.</p><p><strong>3. Shorter the DB retrieval time(computation time), the smaller the recaching determination interval, the lower the recaching probability.</strong></p><p>Therefore, a clear understanding of the algorithm that determines to recache in PER is required. I will elaborate my explanation with the below-shown image.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0paqb7gdRIfNOHoXONmKLw.png" /></figure><p>First of all, the algorithm that determines recaching is the same as the above equation. Actual DB retrieval time(computation time) is multiplied by a probabilistic random value(<strong><em>-log(random()</em></strong>) and determines whether to recache or not, in comparison to the remaining expiration time(TTL).</p><p>For example, in the case of DB retrieval time of 500 <em>m</em>s, the probability of recaching is 10% when <em>1.0 ~ 0.9</em> seconds are left to the expiration time. As shown in the graph above, when the random value is <em>0.1</em>, <br>500ms * 2.3(-log(0.1)) = 1150ms is calculated, and since it is larger than the actual TTL (<em>900 ~1000 ms</em>), it will be recached. Using the rest of the remaining random values<em>(table 0.2 ~ 0.9)</em> since the calculation result is smaller than the remaining expiration time, it won’t be recached. In the blue array(right) you can see the distribution range of random values that enable recaching at each time and check the recaching probability accordingly.</p><p>In practical real applications, since most DB retrieval times take from several to tens of milliseconds, if the corresponding equation is applied, the recaching determination interval is significantly reduced, thus it is less likely to recache even if there are many received requests. Of course, using a constant called <strong>beta</strong> it is possible to increase the range, however, it is very inconvenient for a user to apply constants differently considering the DB retrieval time for each API to which the algorithm is applied.</p><h3>Summary of Results and Requirements</h3><p>Now that we have understood the concept, let&#39;s define the constraints for PER algorithm in order to apply it in a real working environment.</p><ul><li>Cache expiration at the exact time.</li><li>High tps requests (at minimum higher than 50tps).</li><li>Longer the DB retrieval time, the more effective it is.</li></ul><p>Due to the above constraints, there are many problems when it comes to applying PER to ARCUS Common Module that actually operates in the real-world application environment. Therefore, it is necessary to define the requirements of JaM2in to apply to the ARCUS Common Module and attain a suitable algorithm.</p><ul><li>Recaching is possible even if there is a cache expiration error.</li><li>Recaching is possible even at the low tps.</li><li>Recaching is possible even for cache data with a short DB retrieval time (recache regardless of DB retrieval time)</li><li>Fast response speed within several to tens of milliseconds in all sections, DB retrieval and write requests without duplication.<br>○ After determining recaching on the request thread, it requests recache on the background thread.<br>○ In the case of PER, recaching is requested when the remaining expiration time is very short. Hence, if it is processed in the background thread, recaching may not be completed before expiration.</li></ul><p>Through these requirements, from the perspective of implementation, we designed and applied the new algorithm that can prevent cache stampede.</p><h3>Sustainable Caching</h3><p>Sustainable recaching(SUS) is a method designed by applying the PER algorithm. This method uses 10% of the cache expiration time as computation time instead of the actual DB retrieval time. Hence, the determination interval of recaching gets longer and allows recaching to take place even at low requests.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*A9yx1Vy65oCJRSP8Tjb0AQ.png" /></figure><p>Instead of simply expanding the range using a constant(beta), it uses 1/10 of the expiration time by specifying it as the DB retrieval time(computation time). Therefore, this is the form of using cached items as much as 90% of their expiration time when their TTL remains 10% and performing recaching through a calculated probability based on that remaining 10%.</p><p>For example, if the expiration setting time is more than 10 seconds, the minimum time corresponding to 1/10 is <em>1</em> second, and since the recaching takes place within the range, recaching will be done even if a request is received with a small request amount corresponding to <em>1tps</em>.</p><p>In addition, in order to reduce response delays, we did not directly perform caching logic when requesting recache, but rather asked to perform recaching operations in the background thread with sufficient time, and added redundant retrieval and write prevention.</p><p>A short summary of the types and properties of recaching is as follows.</p><ul><li><strong>PER</strong><br>○ Prevents cache misses before the cache expires by recaching.<br>○ Uses DB retrieval time (computation time) for recaching.<br>○ Recaching proceeds immediately from the request processing thread as it approaches the expiration time.</li><li><strong>SUS</strong><br>○ PER and reaching determination algorithm logic are the same.<br>○ Recache using the expire time ratio when DB retrieval time (computation time) is less than 10% of expiration time.<br>○ Recaching proceeds in the background thread to prevent duplicated DB retrievals and cache writes.</li><li><strong>Properties</strong></li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*o2Zeg9Fd4JCdlF_06eCP3w.png" /></figure><p>The comparison of the corresponding algorithms through the example is as follows (DB retrieval time: 100ms, expire time: 30s)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7qxtYaUJ5XAEMK8QN0TFtQ.png" /></figure><p>Recaching range of the example is shown in the graph as follows.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Ou3v0hi5Sokj3Oid62oLXA.png" /></figure><h3>SUS Algorithm in Application Environment: Test</h3><p>In order to compare SUS algorithm performance, the test was conducted under the same environment and conditions as PER.</p><ul><li><strong>100tps</strong> (cache miss: 0-times, response latency: NONE, duplicated writes: 1-times)</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0lMm8vVLJLwI5fXy8nGheg.png" /></figure><ul><li><strong>10tps</strong> (cache miss: 0-times, response latency: NONE, duplicated writes: 1-times)</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*E7KHsI0MiuOprSVr42bb7w.png" /></figure><p>As it is clear from the graph, even when the algorithm is applied according to the actual requirements, it has a great effect on the results.</p><p>Since cached data is constantly retrieved without delay in response, it can have a similar response effect to receiving a response by caching the items as a sticky item. In addition, when recache is requested, by preventing duplicated retrievals and writings, we were able to reduce the load of DB as well.</p><h3>Conclusion</h3><p>In conclusion, we have checked the possible problems that may arise from the expiration time setting of the cache item. Among them, I have introduced you to the problem called a<strong><em> cache stampede</em></strong> that occurs when a lot of requests are received at the same time and how to solve it. We have implemented tests in order to apply to applications and designed a sustainable caching method that is applicable to ARCUS Common Module. SUS algorithm can give a fast response to the users not only for a large but also for a small number of requests regardless of the DB’s retrieval time. The future plan is to further enhance the SUS algorithm, with the additional features listed below.</p><ul><li>Simultaneous request test by applying it to multiple APIs.</li><li>In order to perform reaching in the background, derivate the number of <strong><em>threads</em></strong> that are required, and the right size of the <strong><em>queue </em></strong>according to the number of application requests.</li><li>Comparison and quantification of the difference in performance(WAS throughput, DB load level) from the previous one when the Sustainable caching method was introduced.</li></ul><p>Improvement of the algorithm is also needed.</p><ul><li>When the expiration time is less than 1 second (in case of 1 second, DB retrieval time is set to 100ms), improve the algorithm that becomes the same as PER</li></ul><p>As we have already mentioned Sustainable caching is an algorithm that reflects the requirements to solve the cache stampede problem, and there is still some room for improvements to be made. In the future, even after SUS is applied to the application, through many tests we will improve and modify the algorithm. Stay with us for more updates.</p><p><strong>Reference:</strong></p><p><strong>▪☞</strong> <a href="https://cseweb.ucsd.edu//~avattani/papers/cache_stampede.pdf">Optimal Probabilistic Cache Stampede Prevention</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=468454bdd3b3" width="1" height="1" alt=""><hr><p><a href="https://medium.com/jam2in/sustainable-caching-method-in-arcus-and-how-to-apply-it-468454bdd3b3">Sustainable Caching Method in ARCUS and How To Apply It</a> was originally published in <a href="https://medium.com/jam2in">JaM2in</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ARCUS에서 지속 가능한 캐싱 적용 방안]]></title>
            <link>https://medium.com/jam2in/arcus%EC%97%90%EC%84%9C-%EC%A7%80%EC%86%8D-%EA%B0%80%EB%8A%A5%ED%95%9C-%EC%BA%90%EC%8B%B1-%EC%A0%81%EC%9A%A9-%EB%B0%A9%EC%95%88-bd4e27637bea?source=rss----c55eb3541815---4</link>
            <guid isPermaLink="false">https://medium.com/p/bd4e27637bea</guid>
            <category><![CDATA[caching]]></category>
            <category><![CDATA[arcus-cache-cluster]]></category>
            <category><![CDATA[algorithms]]></category>
            <category><![CDATA[cache-stampede]]></category>
            <category><![CDATA[software-engineering]]></category>
            <dc:creator><![CDATA[moonseop kim]]></dc:creator>
            <pubDate>Thu, 23 Sep 2021 08:36:22 GMT</pubDate>
            <atom:updated>2021-10-25T05:01:51.460Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*spSRnJjPk4fFajXwoP8n0Q.png" /></figure><p>일반 사용자를 대상으로 하는 대규모 응용에서는 데이터(특히, hot data) 조회 시에 DB로 많은 요청이 몰려 높은 부하가 발생합니다. 이러한 DB 단의 부하를 줄이기 위해, 자주 조회되는 데이터를 응용의 분산 캐시에 저장하여 신속하게 응답을 제공하도록 구현하는 것이 일반적인 방법입니다.</p><p>캐시를 응용에 적용할 때는 Demand-fill 방식을 많이 활용합니다. 데이터 조회 요청이 들어올 때 캐시에 데이터가 있다면 캐시의 데이터를 응답해주고, 없다면 DB 조회한 후 캐시에 데이터를 저장하고 응답해주게 됩니다. 따라서 캐시 데이터가 존재하는 동안은 빠른 응답을 제공해 줄 수 있습니다.</p><p>이 방식은 DB의 데이터가 수정될 때 캐시의 데이터와 불일치한다는 문제점이 있습니다. 그래서 DB와 캐시의 데이터 불일치 현상을 줄이기 위해 Expire time or Time-To-Live(TTL)를 설정합니다. (Demand-fill 방식 및 캐시를 응용에 적용하는 자세한 방법은 <a href="https://medium.com/jam2in/java-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-%EA%B8%B0%EB%B3%B8-%ED%8C%A8%ED%84%B4%EC%9D%98-%EC%BA%90%EC%8B%9C-%EC%A0%81%EC%9A%A9%EC%9D%84-%EB%8F%95%EB%8A%94-arcus-%EA%B3%B5%ED%86%B5-%EB%AA%A8%EB%93%88-672ae54c32ce">ARCUS 공통 모듈</a>을 참고해주세요) 이러한 특징으로 인해 아래와 같은 문제가 생길 수 있습니다.</p><ol><li>캐싱된 경우와 캐싱 만료된 경우에 요청에 대한 응답속도 차이 존재</li><li>캐시 데이터 만료 직후부터 데이터가 다시 캐싱될 때까지 수많은 조회 요청이 DB로 몰리는 현상</li></ol><p>해당 문제는 캐시가 만료되어 DB 조회를 하면서 발생하는 문제이며 특히 2번의 문제를 cache stampede라고 지칭합니다.</p><h3>Cache stampede</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vELAD4_Kzc5GfED_jf30-w.jpeg" /></figure><p>stampede란 많은 동물이 갑자기 빠르게 같은 방향으로 돌진하는 현상을 말합니다. cache stampede는 캐시 만료로 인해 많은 데이터 조회 요청이 DB로 갑자기 몰려 DB 부하 증가 및 그로 인한 조회 속도가 저하되는 현상으로 아래와 같은 문제를 일으킬 수 있습니다.</p><ul><li>응용의 응답시간 지연</li><li>만료된 캐시 데이터를 다시 DB에서 조회하여 캐싱하는 작업이 중복하여 발생 즉, 캐시에 중복 쓰기(duplicate write) 발생</li></ul><h3>Cache stampede 방지대책</h3><p>Cache stampede 문제를 해결하기 위한 논문이 VLDB라는 국제 학술대회에서 발표되었고, 그 <a href="https://cseweb.ucsd.edu//~avattani/papers/cache_stampede.pdf">논문</a>에서 소개된 몇 가지 해결법은 아래와 같습니다.</p><ul><li><strong>External re-computation<br>○</strong> 주기적으로 백그라운드 프로세스에서 캐시 아이템을 재생성하여 cache miss를 방지<br>○ 단점 : 주기적으로 재생성하는 아이템 목록(key 목록)이 필요</li><li><strong>Locking<br>○</strong> 중복으로 DB 조회 및 캐시 쓰기가 일어나지 않도록 cache miss가 발생한 처음의 요청에 Lock을 부여해 캐시를 갱신<br>○ 단점 : Cache miss 발생에 따른 중복 조회 및 쓰기의 문제만 해결 가능, 캐시 만료 시 응답지연 문제는 지속</li><li><strong>Probabilistic Early Expiration(PER)<br>○</strong> 확률적 알고리즘을 이용해 캐시 데이터가 만료되기 전 계산된 확률로 미리 캐시를 갱신</li></ul><p>이 중 논문에서 자세하게 제시된 해결법은 PER입니다. PER을 그림으로 설명하면 아래와 같습니다.</p><ul><li><strong>기존의 캐싱 형태</strong></li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/930/1*D1lCOzs_FtJwCBLtvcNzPg.png" /></figure><p>파란 구간은 캐싱이 적용되어있는 구간이고, 하얀 구간이 캐시가 만료(expired)된 구간입니다. 하얀 구간에서 cache miss가 발생하므로 DB로 조회 요청이 몰리는 구간이 됩니다.</p><ul><li><strong>PER을 적용한 경우의 형태</strong></li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/931/1*yzuwcxkafdOIYoA6s7YVdw.png" /></figure><p>캐시 아이템이 만료되기 전 재캐싱을 하여 만료 없이 캐싱이 계속 유지되는 형태입니다. 위의 그림(기본 캐싱)과 비교하여 보면 만료보다 앞서서 재캐싱이 일어나 만료가 발생하지 않는 것을 볼 수 있습니다.</p><p>PER은 요청을 처리하는 스레드가 캐시 데이터를 조회할 때마다 남은 만료 시간 대비 임의(random) 확률로 재캐싱을 수행하는 방안으로, 만료시간이 다가올수록 재캐싱 확률이 증가합니다. (확률을 이용한 알고리즘의 자세한 설명은 뒤에서 계속됩니다.)</p><p>그렇다면 실질적으로 해당 알고리즘이 어떠한 효과를 얻을 수 있는지 간단한 테스트를 통해 알아보겠습니다.</p><h3>Cache stampede 해결방안 테스트</h3><p>기본 캐싱과 PER 적용 경우에 캐시 만료 시의 효과를 비교해 보도록 하겠습니다. 테스트를 위한 환경은 아래와 같습니다.</p><ul><li>테스트 조건</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/727/1*fehWK59vIQP4IV-chxlz0g.png" /></figure><ul><li>Jmeter 설정</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*EvapLvYEGYM6kGZivkoEAg.png" /></figure><ul><li>측정 요소<strong><br>○ </strong>cache miss 발생 횟수<br><strong>○ </strong>cache stampede로 인한 응답 지연 속도<br><strong>○ </strong>cache stampede로 인한 중복 쓰기 횟수</li></ul><p>해당 기준으로 아무것도 적용하지 않는 기본 캐싱과 cache stampede 방지를 위해 PER을 적용하여 테스트한 결과는 아래와 같습니다.</p><ul><li>캐싱 (cache miss : 4회, 응답지연 : 500ms, 중복쓰기 : 242회)</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Z4okH0tsbBpRw6aJH0cBWQ.png" /></figure><ul><li>PER (cache miss : 0회, 응답지연 : 100~200ms, 중복쓰기 : 43회)</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eIkeKRHglSGbQCaR3zCK4g.png" /></figure><p>결과를 살펴보면 기본 캐싱에서는 캐시가 만료되어 cache miss가 발생했고, 많은 요청이 한꺼번에 DB에 몰려 cache stampede 현상이 발생했습니다. 그래서 DB단에서 응답지연 현상이 발생했습니다. 그뿐만 아니라 cache miss로 인하여 모든 요청이 아이템을 캐싱하기 위해 중복으로 DB 조회 및 캐시 쓰기를 하는 현상이 발생하였습니다.</p><p>반면에 PER을 적용했을 경우는 미리 재캐싱이 이루어졌기 때문에 만료가 예정된 모든 구간에서 cache miss가 발생하지 않았고, 재캐싱 수행자들만 DB를 조회하여 응답 시간과 중복쓰기 횟수가 현저히 줄어들었음을 알 수 있습니다. 이를 통해 PER이 cache stampede 방지에 효과가 있음을 확인할 수 있었습니다.</p><h3>실제 응용 환경 적용을 위한 테스트</h3><p>실제 응용에서는 DB 조회 시간이 대부분 수~ 수십ms 정도이며, 캐시 아이템의 만료시간도 짧게는 수십초 ~수분까지 설정하여 캐시를 사용하고 있습니다. PER이 실제 응용 환경에 적용이 가능한지 알아보기 위해 추가로 테스트를 진행해 보았습니다.</p><ul><li>테스트 조건</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/736/1*QKdYVPKhbb6viI8mUczO2Q.png" /></figure><ul><li>Jmeter 설정</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*zq5Shwj6LZiBkXt2b3dgDw.png" /></figure><ul><li>측정 요소<br><strong>○ </strong>cache miss 발생 횟수<br><strong>○ </strong>cache stampede로 인한 응답 지연 속도<br><strong>○ </strong>cache stampede로 인한 중복 쓰기 횟수</li></ul><p>운영 환경에 맞춘 테스트의 결과는 아래와 같습니다.</p><ul><li>100tps (cache miss : 5회, 응답지연 : 0 ~ 100ms, 중복쓰기 : 95회)</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jGEM--o6iBj2GSfEX0pYqA.png" /></figure><ul><li>10tps (cache miss : 10회, 응답지연 : 100ms, 중복쓰기 : 20회)</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JydcJqRaHVlo1APLLUdlDw.png" /></figure><p>실제 응용 환경에 맞추어 테스트 했을 경우 처음 테스트 결과와는 다른 결과가 나오게 됩니다.</p><p>처음 테스트와 비교하여 DB조회 시간만 달라진 100tps에서는 이전과 다르게 일부는 재캐싱, 일부는 cache miss 및 그로 인한 cache stampede 현상(응답지연과 중복 DB 조회 및 캐시 쓰기)이 반복적으로 발생하였습니다. 그리고 10tps에서는 모든 만료 구간에서 재캐싱이 일어나지 않고 cache miss가 발생했습니다. 왜 이런 결과가 발생한 것 일까요? 그 이유는 아래와 같습니다.</p><h3>응용 환경 적용 테스트를 통한 문제점</h3><ol><li><strong>ARCUS cache는 초 단위의 만료시간 측정으로 정밀하게 실제 시간에 만료되지 않음</strong><br><strong>○</strong> 실제 ms 단위로 재캐싱 여부를 결정하는 PER 알고리즘의 적용에 제한</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/479/1*nFnvJP6JbgL3y998dlwQzw.png" /></figure><p>ARCUS 내부에서는 아이템 만료를 위한 시스템 계산이 초 단위 형태로 수행됩니다. 위의 그림을 통해 설명하겠습니다. 서로 다른 캐시 아이템을 0~1초 사이의 다른 지점에서 만료시간(expire time)을 3초로 설정하였을 경우 실제 만료는 동시에 수행됩니다. 예를 들어 위 그림처럼 0초 지점에서 3초를 설정했다면 3초에 만료가 되며, 0.5초 지점에서 3초를 설정하였다면 3.5초의 지점이 아닌 3초 지점에서 만료가 됩니다.그 이유는 ARCUS 내부에서는 초 단위의 타이머(timer)를 가지고 캐시 아이템의 만료 처리를 하기 때문입니다.</p><p>응용에서 만료시간을 ms 단위까지 자세하게 측정하더라도 ARCUS에서는 초 단위로 만료를 처리하기 때문에 응용과 ARCUS 사이에 ms 만큼오차가 발생하며, 이는 재캐싱 여부를 판별할 때에도 오차로 인한 영향을 미치게 됩니다. 위 100tps 테스트에서 번갈아서 반복 형태로 cache miss가 일어난 이유의 원인이 해당 문제로 인해 발생한 것입니다.</p><p><strong>2. tps가 낮은 요청은 재캐싱 확률이 낮음</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/720/1*WAW5i6FPVz3gXUSJ-irweA.png" /></figure><p>실제 응용에서 초당 100회 정도의 요청이 있는 캐시 데이터는 hot data에 해당됩니다. 하지만 응용에서 캐싱하는 데이터는 대부분 hot data가 아닐것 입니다. 초당 수회 또는 그 이하의 드문 요청이면, 위의 오른쪽 그림과 같이 재캐싱 여부를 판별하는 구간에 요청이 들어오지 않아서 재캐싱은 이루어지지 않고 cache miss가 발생합니다. 그 결과는 위 테스트 10tps의 그래프를 통해 보실 수 있습니다.</p><p><strong>3. DB 조회 시간(computation time)이 짧을 수록 재캐싱 판별구간이 줄어들어 재캐싱 확률이 감소</strong></p><p>해당 이유에 대해서는 PER에서 재캐싱을 판별하는 알고리즘에 대한 지식이 필요합니다. 쉽게 그림으로 설명해 보겠습니다.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/806/1*2HBq2jyhoknhvLVNniwRkA.png" /></figure><p>일단 재캐싱을 판별하는 알고리즘은 위의 수식과 같습니다. 실제 DB 조회 시간(computation time)에 확률적인 랜덤 값(-log(random())을 곱하여 잔여 만료 시간(TTL)과 비교해 재캐싱 여부를 결정합니다.</p><p>예를 들어 500ms의 DB 조회 시간의 경우 만료시간이 1.0 ~ 0.9초가 남은 시점에 재캐싱이 이루어질 확률은 1/10입니다. 위 그래프에서 보시는 것처럼 random 값이 0.1일때 500ms * 2.3(-log(0.1)) = 1150ms가 계산되어 실제 TTL(900 ~ 1000ms) 보다 크므로 재캐싱이 이루어지고 나머지의 랜덤 값(표 0.2 ~ 0.9)을 사용하여 계산한 결과는 잔여 만료 시간보다 작기 때문에 재캐싱이 이루어지지 않습니다. (노란색 그래프 안에 각 시간 별 random 값의 분포를 보실 수 있으며 그래프를 통해서 시간별 재캐싱 확률을 계산해 보실 수 있습니다.)</p><p>실제 응용에서는 DB 조회 시간이 수~ 수십ms 정도이기 때문에 해당 수식을 적용한다면 재캐싱 판별 구간이 상당히 줄어 많은 요청이 들어오더라도 재캐싱 요청을 할 가능성이 낮아집니다. 물론 범위를 늘려주기 위해 beta라는 상수를 이용해 설정할 수 있지만, 알고리즘을 적용한 API마다 DB조회 시간을 고려하여 상수를 다르게 적용하는 일은 사용자로서는 대단히 번거롭습니다.</p><h3>결과 요약 및 요구사항</h3><p>해당 내용을 정리하여 PER이 실제 환경에서 적용되기 위한 제약 조건을 정의하면 아래와 같습니다.</p><ul><li>정확한 만료시간에 캐시 만료</li><li>높은 tps의 요청량 (최소 50tps 이상)</li><li>DB 조회 시간이 클수록 효과적</li></ul><p>해당 조건은 실제 서비스를 하는 ARCUS 공통모듈에 적용하기에는 제약사항이 많습니다. 그래서 공통모듈에 적용하기 위해 필요한 잼투인(JaM2in)만의 요구사항을 정의하고 그에 맞는 방안 도출이 필요했습니다.</p><ul><li>캐시 만료 오차 범위 내에서 재캐싱</li><li>낮은 tps에서도 재캐싱 가능</li><li>DB 조회 시간이 짧은 캐시 데이터에 대해서도 재캐싱 가능 효과 (DB 조회 시간과 무관하게 재캐싱)</li><li>지연시간 없이 모든 구간에서 수 ~ 수십ms 이내의 빠른 응답속도, 중복없는 DB조회 및 쓰기 요청<br>○ 요청 스레드에서 재캐싱 판별 후 백그라운드 스레드에 재캐싱 요청 <br>○ PER의 경우 만료시간이 아주 짧게 남았을 때재캐싱 요청을 하므로 백그라운드 스레드에서 재캐싱 수행시 짧은 만료 시간으로 인해 만료전에 캐싱을 완료하지 못하고 캐시 만료가 일어날 수 있음</li></ul><p>위와 같은 요구사항을 통해 조금 더 구현 관점에서 cache stampede를 방지 할 수 있는 알고리즘을 고안하여 적용하게 되었습니다.</p><h3>지속가능 캐싱 (sustainable caching)</h3><p>지속가능 재캐싱 방법은 PER 알고리즘을 응용하여 고안된 방법입니다. 해당 방법은 DB 조회 시간 대신에 캐시 만료 시간의 10% 기준으로 PER 알고리즘을 적용하여 재캐싱 여부를 판별하는 방법으로, 낮은 요청에도 재캐싱이 이루어지게 합니다.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/983/1*8CfpOOHcGuK6nRHJugvHqg.png" /></figure><p>단순히 상수(beta)를 이용해 범위를 확장하지 않고, 만료 시간(expire time)의 1/10을 DB 조회 시간(computation time)으로 지정하여 사용합니다. 이것은 실제 응용 서비스에서 캐시 아이템의 TTL이 10% 남았을 때 재캐싱을 요청하는 형태로 캐싱 된 아이템을 만료시간의 90%만큼 사용하고 나머지 10%의 지점에서 계산을 통해 재캐싱을 수행합니다. 또한 DB 조회 시간에 관계없이 만료 시간을 통한 캐싱이 이루어지므로 재캐싱 범위에 영향이 없게 됩니다.</p><p>예를들어 만료설정 시간이 10초 이상일 경우 1/10에 해당하는 최소 시간은 1초로 해당 범위에서 재캐싱이 이루어지기 때문에 1tps에 해당하는 작은 요청량으로 요청이 들어와도 재캐싱이 이루어 지게 됩니다.</p><p>추가적으로 응답 지연을 제거하기 위해 재캐싱 요청 시 직접 캐싱 로직 수행을 하는 것이 아니라 충분한 시간을 가지고 백그라운드에서 재캐싱 작업이 수행되도록 요청을 하며, 중복 조회 및 쓰기 방지 기능도 추가하였습니다.</p><p>간단하게 재캐싱 유형 및 속성을 간단하게 정리하여 소개하면 아래와 같습니다.</p><ul><li><strong>PER</strong><br><strong>○ </strong>캐시 만료가 이루어지기 전 미리 재캐싱으로 miss 방지<br><strong>○ </strong>DB 조회시간(computation time)을 활용한 재캐싱<br>○ 재캐싱 시 만료시간에 근접하여 재캐싱 진행하므로 해당 스레드에서 바로 재캐싱</li><li><strong>SUS</strong><br><strong>○ </strong>PER과 재캐싱 판별 알고리즘 로직 동일<br><strong>○ </strong>DB조회 시간(computation time)이 만료시간(expire time)의 10%미만 일 경우 expire time 비율을 활용해 재캐싱<br><strong>○ </strong>재캐싱 시 백그라운드 프로세스에서 진행하며 중복 DB 조회 및 캐시 쓰기 방지</li><li><strong>속성</strong></li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/757/1*BA-V2LD4DhXxa0zf1DYXJg.png" /></figure><p>해당 알고리즘을 예제를 통해 비교 하면 아래와 같습니다 (DB조회 시간 : 100ms, 만료시간 : 30s)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/757/1*PqdQ4ROW-CwqUXlxCRzV5w.png" /></figure><p>그래프로 예제의 재캐싱 범위를 확인하면 아래와 같습니다.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/705/1*AigNKUBw18H-aVEQ3ipOAA.png" /></figure><h3>응용 환경에서의 SUS 방법 테스트</h3><p>SUS 적용 성능 비교를 위하여 PER과 동일한 환경과 조건에서 테스트를 진행했습니다.</p><ul><li>100tps (cache miss : 0회, 응답지연 : NONE, 중복쓰기 : 1회)</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*k_WfRipf_Kb-15XmQkZX_g.png" /></figure><ul><li>10tps (cache miss : 0회, 응답지연 : NONE, 중복쓰기 : 1회)</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*XD21G7g_Ll219sL0cnLudw.png" /></figure><p>결과는 그래프로도 한눈에 볼수 있듯이, 실제 요구사항대로 알고리즘이 적용되어 큰 효과를 볼 수 있었습니다.</p><p>응답 지연없이 계속해서 캐싱된 데이터가 조회 되므로 아이템을 sticky하게 캐싱하여 응답받는 것과 비슷한 응답 효과를 낼 수 있습니다. 또한 재캐싱 요청시에도 중복 조회 및 쓰기 방지를 통해 DB의 부하도 경감할 수 있었습니다.</p><h3>마치며</h3><p>캐시 아이템의 만료시간 설정으로 인해 발생할 수 있는 문제를 살펴보았습니다. 그 중 많은 요청이 동시에 들어왔을 때 발생하는 cache stampede에 대한 문제와 그 문제를 해결하는 방법에 대해 알아보고, 응용에 적용하기 위한 테스트와 ARCUS 공통 모듈에 적용하는 sustainable caching 방법까지 함께 소개해 드렸습니다. 해당 방법은 많은 요청량 뿐만 아니라 적은 요청량에서도 DB의 조회시간에 관계없이 사용자에게 일정하게 빠른 시간내에 응답을 줄 수 있는 방법입니다. 앞으로 응용에 적용하기 위해 추가적인 준비 사항은 아래와 같습니다.</p><ul><li>여러개의 API에 적용하여 동시 요청 테스트</li><li>백그라운드에서 재캐싱을 수행하기 위해 응용의 요청 수 별로 필요한 Thread 개수와 Queue의 적정 크기 도출</li><li>지속가능 캐싱 방법 도입시 이전과의 성능상 차이점 (WAS 처리량, DB 부하정도) 비교 및 수치화</li></ul><p>또한 해당 알고리즘의 개선도 필요합니다.</p><ul><li>expire time이 1초 미만일 경우 (1초일 경우 DB조회 시간은 100ms로 설정됨) PER과 동일해 지는 알고리즘 개선</li></ul><p>위와 같이 sustainable caching은 cache stampede를 해결하기 위해 요구 사항을 반영한 하나의 방안이며 개선점과 테스트할 사항이 존재합니다. 이후에도 응용에 적용하여 많은 테스트를 통해 수정과 개선이 이루어 질 것입니다. 이후의 포스팅도 기대해 주시기 바랍니다.</p><h3>참고</h3><ul><li><a href="https://cseweb.ucsd.edu//~avattani/papers/cache_stampede.pdf">Optimal Probabilistic Cache Stampede Prevention</a></li><li><a href="https://www.wallpaperflare.com/wildebeest-herd-painting-stampede-tanzania-africa-d300-creative-commons-wallpaper-tpniq">stampede 이미지 참조</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=bd4e27637bea" width="1" height="1" alt=""><hr><p><a href="https://medium.com/jam2in/arcus%EC%97%90%EC%84%9C-%EC%A7%80%EC%86%8D-%EA%B0%80%EB%8A%A5%ED%95%9C-%EC%BA%90%EC%8B%B1-%EC%A0%81%EC%9A%A9-%EB%B0%A9%EC%95%88-bd4e27637bea">ARCUS에서 지속 가능한 캐싱 적용 방안</a> was originally published in <a href="https://medium.com/jam2in">JaM2in</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Introducing Front Cache of ARCUS Spring]]></title>
            <link>https://medium.com/jam2in/introducing-front-cache-of-arcus-spring-11434b1c4143?source=rss----c55eb3541815---4</link>
            <guid isPermaLink="false">https://medium.com/p/11434b1c4143</guid>
            <category><![CDATA[spring]]></category>
            <category><![CDATA[arcus-cache-cluster]]></category>
            <category><![CDATA[java]]></category>
            <category><![CDATA[cache]]></category>
            <category><![CDATA[front-cache]]></category>
            <dc:creator><![CDATA[N.M.G.]]></dc:creator>
            <pubDate>Fri, 27 Aug 2021 09:19:50 GMT</pubDate>
            <atom:updated>2021-10-21T09:13:11.049Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/803/1*h4CDmzJ-0mzV9ZbWmsLGKg.png" /></figure><h3>Front Cache</h3><p>Remote cache solutions that perform caching on separate servers such as ARCUS Cache have advantages in sharing data with each other that has been cached on multiple applications. However, on the remote cache, data response time sensitively gets affected depending on the occurrence of a large amount of network traffic or lack of resources due to a large number of temporary requests on the software. These kinds of problems can be solved by scaling a cluster or upgrading the system’s specifications, but in that case, there is a burden of increasing operation costs. On the other hand, there is also a software-wise solution that is relatively less expensive, and that is, caching data in the front of the remote cache using the local memory of the application. This method is called <strong>Front Cache</strong>, also known as <em>local caching</em> because it is cached in local memory. Caching data to the local memory is much faster than caching it to the remote server over the network. One of the advantages of Front Cache is its being not affected by network traffic, and let’s say if you are managing traffic load data, such as events, announcements, trend news then by applying Front Cache you can guarantee fast response time and high throughput for your application.</p><p>In order to be able to do that, we specifically implemented <a href="https://docs.spring.io/spring-framework/docs/4.1.x/spring-framework-reference/html/cache.html">Spring Cache</a> of Spring Framework for ARCUS and added the interface of Front Cache to the latest version of the <a href="https://github.com/naver/arcus-spring">ARCUS Spring</a>(1.13.3)’s library. In this article, I will introduce you to the Front Cache features of ARCUS Spring, its behaviors, usage method, and precautions to be aware of.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WOhBRX8KQ5QQ-gNGrDZcfg.png" /></figure><h3>How Front Cache works</h3><p>The Front Cache provided by ARCUS Spring performs caching before the ARCUS. The organized behavioral structure based on the request types is as follows.</p><h4>Retrieval of Cache Data</h4><ol><li>First, in order to <em>retrieve</em> data, application calls get API of Spring Cache interface.</li><li>Check if the data exists in the Front Cache. If it is, then we return the requested data.</li><li>If data does not exist in the Front Cache, check if the data exists in the ARCUS Cache. If it is, we store the data in the Front Cache and then return the requested data.</li></ol><h4>Store Cache Data</h4><ol><li>In order to <em>save</em> data, application calls put API of Spring Cache.</li><li>Then request storing data into ARCUS Cache.</li></ol><p>3–1. (Option 1) Only when the data storing request to ARCUS is successfully completed, then store the data to the Front Cache.</p><p>3–2. (Option 2) Regardless of the result of data storing request to ARCUS, store the data to the Front Cache.</p><h4>Removal of Cache Data</h4><ol><li>In order to <em>delete</em> data, application calls evict/clear API of Spring Cache.</li><li>Then request data removal from ARCUS Cache.</li></ol><p>3–1. (Option-1) Only when the data removal request from ARCUS is successfully completed, remove the data from the Front Cache.</p><p>3–2. (Option-2) Regardless of the data removal request from ARCUS, remove the data from the Front Cache.</p><p>In the cases of storing and removing data, Option 1 is set as default, but you can change it with <a href="https://github.com/naver/arcus-spring#configuration-1">forceFrontCaching</a>. If the ARCUS Cache is not available due to a network issue, Option 2 can still maintain the Front Cache function. If your data changes over time then Option 1, if it isn’t then Option 2 is recommended.</p><h3>How to Use</h3><p><a href="https://github.com/naver/arcus-spring/blob/1.13.3/src/main/java/com/navercorp/arcus/spring/cache/ArcusCache.java">ArcusCache</a> class is specifically implemented for ARCUS from Spring Cache interface and relies on the abstracted <a href="https://github.com/naver/arcus-spring/blob/1.13.3/src/main/java/com/navercorp/arcus/spring/cache/front/ArcusFrontCache.java">interface</a> below for front caching. It provides flexibility and allows to implement the interface directly to use other remote or local caches as front caching for ARCUS as shown below.</p><pre>public interface ArcusFrontCache {<br><br>  Object get(String key);<br>  void set(String key, Object value, int expireTime);<br>  void delete(String key);<br>  void clear();<br>}</pre><p>Although you don’t need to implement the interface by yourself, you can always use the <a href="https://github.com/naver/arcus-spring/blob/1.13.3/src/main/java/com/navercorp/arcus/spring/cache/front/DefaultArcusFrontCache.java">DefaultArcusFrontCache</a> class built into ARCUS Spring. This class caches the data into the application’s local memory and uses the <a href="https://www.ehcache.org/">EhCache</a> library internally. Now we will try to apply this class to Spring-based applications to perform Front Caching before ARCUS Cache.</p><p>First, we create the DefaultArcusFrontCache class then set the dependencies on the ArcusCache class. For the purpose of the testing, we will set TTL(TimeToLive) of the <strong>Front Cache </strong>to 10 seconds, 20 second<em>s </em>shorter than the ARCUS cache.</p><pre>public class ArcusCacheConfiguration {<br><br>  @Bean<br>  public ArcusCache testCache() {<br>      ArcusCache arcusCache = new ArcusCache();<br>      arcusCache.setName(&quot;test&quot;);<br>      arcusCache.setServiceId(&quot;TEST-&quot;);<br>      arcusCache.setPrefix(&quot;TEST&quot;);<br>      arcusCache.setTimeoutMilliSeconds(800);<br>      arcusCache.setArcusClient(arcusClient());</pre><pre>      // <strong>Setting TTL of ARCUS Cache item</strong><br>      arcusCache.setExpireSeconds(30);      </pre><pre>      // <strong>Setting Front Cache Instance</strong><br>      arcusCache.setArcusFrontCache(testArcusFrontCache());</pre><pre>      // <strong>Setting TTL of Front Cache item</strong><br>      arcusCache.setFrontExpireSeconds(10);</pre><pre>     // <strong>Even if Store/Removal requests of ARCUS fail</strong> <br>     // <strong>Set to perform Store/Removal requests of Front Cache</strong><br>      arcusCache.setForceFrontCaching(true);<br>      return arcusCache;<br>  }<br><br>  @Bean<br>  public ArcusFrontCache testArcusFrontCache() {<br>    return new DefaultArcusFrontCache(</pre><pre>      // <strong>Cache name, for each instance names must be unique</strong><br>      &quot;test&quot;, /* <strong>name</strong> */<br>      <br>      // <strong>Maximum number of cache items to be stored</strong><br>      // <strong>If max is exceeded, existing item will be removed by LRU</strong><br>      10000, /* <strong>maxEntries</strong> */</pre><pre>      // <strong>when retrieving item instance from the Front Cache</strong><br>      // <strong>Setting for retrieve the reference of item or copy of it</strong><br>      // <strong>Retrieves the item&#39;s reference when set to false</strong><br>      false, /* <strong>copyOnRead</strong> */</pre><pre>      // <strong>when storing item instance into the Front Cache</strong><br>      // <strong>Setting for storing the reference of item or copy of it</strong><br>      // <strong>Stores the item&#39;s reference when set to false</strong><br>      false /* <strong>copyOnWrite</strong> */<br>    );<br>  }<br><br>  @Bean<br>  public ArcusClientPool arcusClient() {<br>    ArcusClientFactoryBean arcusClientFactoryBean = new ArcusClientFactoryBean();<br>    arcusClientFactoryBean.setUrl(&quot;1.2.3.4:1234&quot;);<br>    arcusClientFactoryBean.setServiceCode(&quot;test&quot;);<br>    arcusClientFactoryBean.setPoolSize(8);<br>    return arcusClientFactoryBean.getObject();<br>  }<br>}</pre><p>Add the ArcusCache instance that you just created to the cache list of CacheManager.</p><pre>@EnableCaching<br>@Configuration<br>public class CacheConfiguration implements CachingConfigurer {<br><br>  @Autowired<br>  private ArcusCache testCache;<br><br>  @Bean<br>  @Override<br>  public CacheManager cacheManager() {<br>    SimpleCacheManager arcusCacheManager =<br>        new SimpleCacheManager();<br>    arcusCacheManager.setCaches(<br>      List.of(testCache)<br>    );<br>    return arcusCacheManager;<br>  }<br><br>  @Override<br>  public KeyGenerator keyGenerator() {<br>    return new StringKeyGenerator();<br>  }<br><br>  @Override<br>  public CacheResolver cacheResolver() {<br>    return null;<br>  }<br><br>  @Override<br>  public CacheErrorHandler errorHandler() {<br>    return null;<br>  }<br>}</pre><p>We have now completed the Front Cache configuration. Now let&#39;s apply <a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/cache/annotation/Cacheable.html">@Cacheable</a> Annotation to the service that you want to apply cache and then check if Front Cache has been applied correctly.</p><pre>@Service<br>public class ProductService {<br><br>  @Autowired<br>  private ProductRepository productRepository;<br><br>  @Cacheable(value = &quot;test&quot;, key=&quot;#product.id&quot;)<br>  public Product get(Product product) {<br>    return productRepository.select(productDto.getId());<br>  }<br>}</pre><p>Every time when you send the request to the service that you have added caching feature, log below from the ArcusCache class will be printed.</p><ul><li>(1) Initially there is no data in ARCUS Cache and Front Cache, therefore the first thing to do is to store data in both.</li><li>(2) Retrieve and return the data from Front Cache. Since we have set TTL of Front Cache for 10 seconds, ARCUS Cache will not look for it until data is expired in the Front Cache.</li><li>(3) When the data stored in Front Cache is expired after 10 seconds, lookup for data from Front Cache will be failed, thus the data will be retrieved from ARCUS Cache, and stored in the Front Cache.</li><li>(4) Same procedure in step (2) will be performed.</li></ul><pre>DEBUG 21-07-16 17:42:05 [ArcusCache:448] - getting value by key: TEST-PRODUCT:1266<br>DEBUG 21-07-16 17:42:05 [ArcusCache:480] - trying to put key: TEST-PRODUCT:1266, value: com.jam2in.arcus.Product ... <strong>(1)</strong> <strong>Stores in ARCUS, Front Cache</strong><br>DEBUG 21-07-16 17:42:07 [ArcusCache:448] - getting value by key: TEST-PRODUCT:1266 <br>DEBUG 21-07-16 17:42:07 [ArcusCache:454] - front cache hit for TEST-PRODUCT:1266 ... <strong>(2) Returns retrieved data from Front Cache</strong><br>DEBUG 21-07-16 17:42:10 [ArcusCache:448] - getting value by key: TEST-PRODUCT:1266<br>DEBUG 21-07-16 17:42:10 [ArcusCache:454] - front cache hit for TEST-PRODUCT:1266 ... <strong>(2)</strong><br>DEBUG 21-07-16 17:42:15 [ArcusCache:448] - getting value by key: TEST-PRODUCT:1266<br>DEBUG 21-07-16 17:42:15 [ArcusCache:454] - front cache hit for TEST-PRODUCT:1266 ... <strong>(2)</strong><br>DEBUG 21-07-16 17:42:16 [ArcusCache:448] - getting value by key: TEST-PRODUCT:1266<br>DEBUG 21-07-16 17:42:16 [ArcusCache:470] - arcus cache hit for TEST-PRODUCT:1266 ... <strong>(3)</strong> <strong>Returns retrieved data from ARCUS Cache since TTL of Front Cache is expired</strong><br>DEBUG 21-07-16 17:42:17 [ArcusCache:448] - getting value by key: TEST-PRODUCT:1266<br>DEBUG 21-07-16 17:42:17 [ArcusCache:454] - front cache hit for TEST-PRODUCT:1266 ... <strong>(4)</strong> <strong>Return retrieved data from Front Cache</strong><br>DEBUG 21-07-16 17:42:18 [ArcusCache:448] - getting value by key: TEST-PRODUCT:1266<br>DEBUG 21-07-16 17:42:18 [ArcusCache:454] - front cache hit for TEST-PRODUCT:1266 ... <strong>(4)</strong></pre><p>The above example shows how a single Front Cache is created. But you can always configure <strong><em>N</em></strong> Front Caches instead of a single one. For example, if you want to use a unique front cache for each service, you can set it up as follows.</p><pre>@Bean<br>public ArcusCache productCache() {<br>    ArcusCache arcusCache = new ArcusCache();<br>    arcusCache.setName(&quot;product&quot;);<br>    ... (<strong>omitted</strong>) ...<br>    arcusCache.setArcusFrontCache(productFrontCache()); </pre><pre>    // <strong>Using Product Front Cache</strong><br>    return arcusCache;<br>}<br><br>@Bean<br>public ArcusFrontCache productFrontCache() {</pre><pre>  // <strong>Create a product Front Cache</strong><br>  return new DefaultArcusFrontCache(<br>   &quot;productFront&quot;, 20000, false, false);<br>}<br><br>@Bean<br>public ArcusCache eventCache() {<br>    ArcusCache arcusCache = new ArcusCache();<br>    arcusCache.setName(&quot;event&quot;);<br>    arcusCache.setPrefix(&quot;EVENT&quot;);<br>    ... (<strong>omitted</strong>) ...<br>    arcusCache.setArcusFrontCache(eventFrontCache());</pre><pre>    // <strong>Using Event Front Cache</strong><br>    return arcusCache;<br>}<br><br>@Bean<br>public ArcusFrontCache eventFrontCache() {</pre><pre>  // <strong>Create an Event Front Cache</strong><br>  return new DefaultArcusFrontCache(<br>  &quot;eventFront&quot;, 10000, false, false);<br>}</pre><p>Please note that Front Cache instances created in the above example do not share data between themselves due to each instance has a different hash table. But if you want to share data of multiple ARCUS Cache instances with Front Cache, then you can create one Front Cache instance as shown below, and assign it to multiple ARCUS Cache instances.</p><pre>@Bean<br>public ArcusCache productCache() {<br>    ArcusCache arcusCache = new ArcusCache();<br>    arcusCache.setName(&quot;product&quot;);<br>    ... (<strong>omitted</strong>) ...</pre><pre>    // <strong>Using share Front Cache</strong>    <br>    arcusCache.setArcusFrontCache(sharedFrontCache()); <br>    return arcusCache;<br>}<br><br>@Bean<br>public ArcusCache eventCache() {<br>    ArcusCache arcusCache = new ArcusCache();<br>    arcusCache.setName(&quot;event&quot;);<br>    arcusCache.setPrefix(&quot;EVENT&quot;);<br>    ... (<strong>omitted</strong>) ...</pre><pre>    // <strong>Using share Front Cache</strong>     <br>    arcusCache.setArcusFrontCache(sharedFrontCache()); <br>    return arcusCache;<br>}<br><br>@Bean<br>  // <strong>Using share Front Cache</strong><br>  public ArcusFrontCache sharedFrontCache() { <br>  return new DefaultArcusFrontCache(&quot;shared&quot;, 50000, false, false);<br>}</pre><h3>Warnings</h3><p>The performance of Front Cache which is stored in the local memory of the application is much faster than a remote cache but it isn’t available in all types of cases. Please take a look at the following issues when using the Front Cache.</p><h4>High Memory Usage</h4><p>Front Cache stores the same data in the memory of all applications, therefore it has the disadvantages of requiring a lot of memory space. Depending on the number of expired and no longer referred cache data, it will cause frequent use of the Garbage Collection of JVM and result in low application performance. Therefore, considering memory usage of the application the maximum size and amount of the data that can be stored in the Front Cache must be estimated. If you are using the DefaultArcusFrontCache class of ARCUS Spring, you can set the maximum number of data that can be stored with the maxEntries property.</p><h4>Data Mismatch</h4><p>Generally, data mismatch occurs between applications due to the multiple applications are unable to share cache data. For example, if you perform a data change request to a particular application, other applications’ Front Cache does not reflect the changed data. Because of this, there is a problem with inconsistent data response for a request in a multi-server environment. We can resolve the data mismatch problem in the following manners:</p><ul><li>We set short TTL for Front Cache. Even in the case of hot data that gets a lot of requests, with a short expiration time, it will still show high performance.</li><li>We set the <strong>Sticky Session</strong> on the load balancer at the front of the application and depending on the session we convey the request to the server that first processed it.</li><li>We only front cache small changes of data.</li></ul><h4>Dual Caching (in case of ARCUS Java Client Usage)</h4><p><a href="https://github.com/naver/arcus-java-client/blob/master/docs/02-arcus-java-client.md#transparent-front-cache-%EC%82%AC%EC%9A%A9">Java Client of ARCUS</a> also has an internal <a href="https://github.com/naver/arcus-java-client/blob/master/docs/02-arcus-java-client.md#transparent-front-cache-%EC%82%AC%EC%9A%A9">Front Cache feature</a>. However, there are some limitations such as TTL, the maximum number of data, and other properties of a Front Cache that must be shared for every cache target. If this limitation doesn’t concern you much, then using only ARCUS Java Client’s Front Cache would be enough. But if you want to use the Front Cache of ARCUS Spring with different properties per cache target then to prevent dual caching you have to disable ARCUS Java Client’s Front Cache function (it’s <strong><em>disabled by default</em></strong>). But if you are switching from ARCUS Java Client’s Front Cache to ARCUS Spring’s Front Cache then you need to directly disable the front cache function of ARCUS Java Client as shown below.</p><pre>ConnectionFactoryBuilder factory = new ConnectionFactoryBuilder();<br>// <strong>Set to 0 to disable the Front Cache. Default value is 0.</strong><br>factory.setMaxFrontCacheElements(0); <br>ArcusClient client = new ArcusClient(SERVICE_CODE, factory);</pre><h3>Conclusion</h3><p>In summary, I have introduced and explained the Front Cache’s features provided by ARCUS Spring. If you are aware of the warnings for the usage of front cache and apply safety measures, then you can experience improvement in the performance of request processing for more applications, rather than using ARCUS Cache alone. However, there are limitations for front cache to be applied. In order to be able to apply front cache to the more cache targeted areas<strong><em> synchronization feature</em></strong> must be included to eliminate inconsistencies in data between other front caches of multiple applications, as well as in ARCUS Cache.</p><p>ARCUS Spring project has been improving steadily, and we will keep you informed on further enhancements and additions to the front cache in our next articles.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=11434b1c4143" width="1" height="1" alt=""><hr><p><a href="https://medium.com/jam2in/introducing-front-cache-of-arcus-spring-11434b1c4143">Introducing Front Cache of ARCUS Spring</a> was originally published in <a href="https://medium.com/jam2in">JaM2in</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Introducing ARCUS Single Cache (Dev.) on AWS Marketplace and How to Use It]]></title>
            <link>https://medium.com/jam2in/introducing-arcus-single-cache-dev-on-aws-marketplace-and-how-to-use-it-9d05067c231d?source=rss----c55eb3541815---4</link>
            <guid isPermaLink="false">https://medium.com/p/9d05067c231d</guid>
            <category><![CDATA[jam2in]]></category>
            <category><![CDATA[arcus-cache-cluster]]></category>
            <category><![CDATA[cache]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[ec2]]></category>
            <dc:creator><![CDATA[N.M.G.]]></dc:creator>
            <pubDate>Wed, 16 Jun 2021 03:41:07 GMT</pubDate>
            <atom:updated>2021-08-13T07:50:15.144Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/363/1*t6fHNW6SK2Y_x8PhVt8dEQ.png" /></figure><p>ARCUS Single Cache(Dev.) is configured as an AMI(Amazon Machine Image) to provide an easy and fast experience of ARCUS on AWS Marketplace. ARCUS Single Cache(Dev.) uses AMI configuration to support one-click deployment to ease the use of ARCUS Cache. You can check the ARCUS Single Cache (Dev.) on AWS Marketplace from here (<a href="https://aws.amazon.com/marketplace/pp/B09297H1KC?qid=1620957097048&amp;sr=0-1&amp;ref_=srh_res_product_title">URL</a>).</p><h4>What’s Amazon Machine Image?</h4><p>An Amazon Machine Image or AMI is a packaged environment containing a software(OS, Application Server) configuration and other additional applications required to set up an instance to deliver a service or a part of it.</p><p>Previously, to use the ARCUS Cache, first you needed to clone it from its Github repository (<a href="https://github.com/naver/arcus">URL</a>) and go through various build/setup processes. This method can be a little bit difficult for first-time users. ARCUS Single Cache (Dev.) solves this difficulty, and it’s much easier and faster to experience ARCUS Cache. In this article, I’ll introduce you to ARCUS Single Cache(Dev.) and how to use it.</p><h3>ARCUS Single Cache (Dev.)</h3><p>ARCUS Single Cache(Dev.) has been implemented as a light version of the ARCUS Cache Cluster to experience the basic features. As the name suggests, ARCUS Single Cache(Dev.) supports a single cache owing to consist of only one node, thus being limited to only some of the features of ARCUS.</p><h4>Configuration Details</h4><p>Besides the clustering feature, ARCUS Single Cache(Dev.) limits available memory and connection resources while providing most of the basic main features for a single cache. Configuration details of ARCUS Single Cache(Dev.) are as follows:</p><blockquote>▪ Memory size: 250MB<br>▪ Connection size: 1024</blockquote><h4>Provided Features</h4><p>ARCUS Single Cache(Dev.) supports simple <strong>key-value</strong> data type and a <strong>collection</strong> (List, Set, Map, B+Tree) data structure that stores and views multiple values in a structured form in a single key. It also provides a prefix feature to form a group and manage keys. The details are as follows.</p><h4>Cache Item</h4><p>In addition to simple <strong><em>key-value</em></strong>, ARCUS Single Cache(Dev.) provides various item types in a <strong><em>collection data structure</em></strong>.</p><ul><li><strong>Key-Value</strong>: a simple key-value item that stores a single value.</li></ul><h4>Collection item</h4><ul><li><strong>List item</strong>: an item that has a double-linked list of data elements.</li><li><strong>Set item</strong>: an item that has an unordered set of unique data elements.</li><li><strong>Map item</strong>: an item that has an unordered set of &lt;field, data&gt; pairs.</li><li><strong>B+tree item</strong>: an item that has a data set sorted by a b+tree key.</li></ul><p>B+tree supports efficiently <em>range</em> search in both backward and forward directions as well as <em>exact</em> search. Each element of b+tree has a unique key and a set of elements is sorted by these unique keys in its b+tree structure.</p><h4>Cache Key</h4><p>Cache key identifies the data to be stored in the ARCUS Single Cache(Dev.). It has a syntax of &lt;prefix&gt;:&lt;subkey&gt;.</p><ul><li>Prefix is a name preceding the cache key. You can group keys stored in a cache server to flush or view stat info in prefix units. prefix can be omitted but it’s recommended to be used as much as possible. prefix can only consist of: Uppercase letters, numbers, (_)underbars, (-)hyphens, (+)plus, and (.)dots characters.</li><li>delimeter (:)is a character used to separate prefix and subkey.</li><li>Subkey is a <em>key</em> commonly used in applications to distinguish cache items. subkey cannot contain spaces, and by default, it is recommended to use only alphanumeric characters.</li></ul><p>So up to this point, I have given you general information about ARCUS Single Cache(Dev.) and from now on I will show you how to use it on AWS.</p><h3>Creating ARCUS Single Cache (Dev.)</h3><p>Before creating ARCUS Single Cache(Dev.) first thing you need to do is create an AWS account. If you don’t have an account, please create an account before proceeding. Now, login into <strong>AWS Management Console(</strong><a href="https://aws.amazon.com/console/"><strong>URL</strong></a><strong>) </strong>and let’s create an EC2 instance to experience ARCUS.</p><p><strong>1: </strong>Click the <strong>Services</strong> tab on the upper left corner of Console.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-5aREmr7TiEy61Aa3uulWg.png" /></figure><p><strong>2: </strong>Choose <strong>EC2</strong> under Compute Category.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*b0lXC2O0V9pyp0eITWakBQ.png" /></figure><p><strong>3:</strong> Once you’re in the EC2 Dashboard, click the <strong>Launch Instance</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/886/1*WxSuQXtMOWhlbPn716B_mg.png" /></figure><p><strong>4:</strong> Now Step-1: Choose an Amazon Machine Image (AMI) page will appear. Choose the <strong>AWS Marketplace</strong> that you’ll see on the left side of the page.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*zJev79p829mIKaGvSgy9nQ.png" /></figure><p>5: <strong>Search</strong> for an arcus, <strong>Select</strong> the <strong>ARCUS Single Cache(Dev.) </strong>and continue.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/934/1*X06SRainQzLmcdwI8zWP9A.png" /></figure><p><strong>6:</strong> Now, on Step-2: Choose an Instance Type, <strong>select the instance type of your choice, </strong>but I’ll go with<strong> </strong>the t2.micro instance, as it&#39;s the vendor’s recommendation, and also Micro instances are free for up to 750 hours a month.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*G-qMgWLeTeyzToxrTei6Bw.png" /></figure><p>Once the instance type selection is completed, you can continue with further steps or you can jump right into Step-7: Review to proceed with a default configuration.</p><p>7: Now that all settings are complete, Step-7: Review page will appear. If you scroll down the page, you can check the port settings (SSH Connect(22), ARCUS ZooKeeper (2181), and ARCUS Memcached (11211).) to access ARCUS. In addition, all IPs (0.0.0.0/0) are opened/allowed by default. This may expose you to security issues, so we recommend checking your IP address and whitelisting it before you start.</p><p>8: Finally, to launch your instance choose an existing or create a new <strong><em>key-pair</em></strong> and download your <strong><em>key-pair (.pem)</em></strong> file. Put the .pem file into the same directory where your EC2 instance will run, it is important for SSH access.</p><h4>Verify ARCUS</h4><p>After connecting to the ARCUS Cache instance, via telnet we will check whether arcus-memcached is running and conduct a simple test. Now please enter your EC2 instance’s public IP and port, as shown below.</p><pre>$ telnet [IP address] 11211</pre><p>Now that you’re connected to ARCUS, try a stats command as shown below. The result of this command will show you ARCUS cache statistics such as PID, version info, memory usage, and computation performance.</p><pre>$ stats</pre><p>In addition, you can try some other commands(<a href="https://www.jam2in.com/arcus-docs/#/arcus-server/ARCUS-Server-Ascii-Protocol/1.13/ch04-command-key-value">URL</a>) on arcus-memcached. <strong><em>Note:</em></strong><em> the current ARCUS Documentation is only available in Korean. But you can use </em><a href="https://papago.naver.com/"><em>papago.naver.com</em></a><em> for general translation.</em></p><h3>Using ARCUS Single Cache (Dev.) with ARCUS Java Client</h3><p>In order to use ARCUS Single Cache(Dev.) with Java application, you need ARCUS Java Client. In the following sections, I will show you the required settings to use Java Client.</p><h4>Environment Settings</h4><p>Once you have established the below-listed environment settings, please create a Java project to proceed.</p><ul><li>Apache Maven (higher than version 4)</li><li>Java (higher than version 1.6)</li><li>Eclipse / Intellij IDEA</li></ul><h4>POM.XML Configuration</h4><p>Once the project has been created, please update your POM file for installing dependencies as shown below.</p><pre><strong>&lt;</strong>project<strong> </strong>xmlns=&quot;http://maven.apache.org/POM/4.0.0&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;<br>xsi:schemaLocation=&quot;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&quot;<strong>&gt;</strong>    </pre><pre>&lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt; <br> <br>  &lt;groupId&gt;com.navercorp.arcus&lt;/groupId&gt;<br>  &lt;artifactId&gt;<strong>arcus-quick-start</strong>&lt;/artifactId&gt;<br>  &lt;version&gt;1.0-SNAPSHOT&lt;/version&gt;<br>  &lt;packaging&gt;jar&lt;/packaging&gt;</pre><pre><strong>  &lt;properties&gt;</strong><br>    &lt;project.build.sourceEncoding&gt;UTF-8<br>    &lt;/project.build.sourceEncoding&gt;<br>  <strong>&lt;/properties&gt;</strong></pre><pre><strong>  &lt;dependencies&gt;</strong><br>  <strong>&lt;!-- ARCUS Java Client Dependency --&gt;</strong><br>    &lt;dependency&gt;<br>      &lt;groupId&gt;com.navercorp.arcus&lt;/groupId&gt;<br>      &lt;artifactId&gt;arcus-java-client&lt;/artifactId&gt;<br>      &lt;version&gt;1.13.0&lt;/version&gt;<br>    &lt;/dependency&gt;<br>    <br> <strong>&lt;!-- logging Dependency --&gt;</strong><br>    &lt;dependency&gt;<br>      &lt;groupId&gt;org.apache.logging.log4j&lt;/groupId&gt;<br>      &lt;artifactId&gt;log4j-core&lt;/artifactId&gt;<br>      &lt;version&gt;2.13.3&lt;/version&gt;<br>      &lt;optional&gt;true&lt;/optional&gt;<br>   <strong> </strong>&lt;/dependency&gt;<br>    &lt;dependency&gt;<br>      &lt;groupId&gt;org.apache.logging.log4j&lt;/groupId&gt;<br>      &lt;artifactId&gt;log4j-api&lt;/artifactId&gt;<br>      &lt;version&gt;2.13.3&lt;/version&gt;<br>      &lt;optional&gt;true&lt;/optional&gt;<br>    &lt;/dependency&gt;<br>    &lt;dependency&gt;<br>      &lt;groupId&gt;org.apache.logging.log4j&lt;/groupId&gt;<br>      &lt;artifactId&gt;log4j-slf4j-impl&lt;/artifactId&gt;<br>      &lt;version&gt;2.13.3&lt;/version&gt;<br>       &lt;exclusions&gt;<br>        &lt;exclusion&gt;<br>          &lt;groupId&gt;org.slf4j&lt;/groupId&gt;<br>          &lt;artifactId&gt;slf4j-api&lt;/artifactId&gt;<br>        &lt;/exclusion&gt;<br>       &lt;/exclusions&gt;<br>       &lt;optional&gt;true&lt;/optional&gt;<br>    &lt;/dependency&gt;<br>    &lt;dependency&gt;<br>      &lt;groupId&gt;org.slf4j&lt;/groupId&gt;<br>      &lt;artifactId&gt;slf4j-api&lt;/artifactId&gt;<br>      &lt;version&gt;1.7.24&lt;/version&gt;<br>   <strong> </strong>&lt;/dependency&gt;<strong><br>  &lt;/dependencies&gt;</strong><br>&lt;/project&gt;</pre><h4>Create HelloARCUS.java</h4><p>Now let&#39;s create HelloArcus.java within the project. You can use the below code as it is, but make sure to enter your <strong><em>EC2 instance’s public IP address</em></strong> as the `ADDRESS` variable.</p><pre>import net.spy.memcached.ArcusClient;<br>import net.spy.memcached.ConnectionFactoryBuilder;import java.util.concurrent.Future;<br>import java.util.concurrent.TimeUnit;</pre><pre><strong>//HelloArcus.java</strong><br>public class HelloArcus {<br>  <br><strong>//Enter your corresponding EC2 IP address into ADDRESS.</strong><br>  private static final String ADDRESS = &quot;<strong><em>YOUR INSTANCE IP</em></strong>:2181&quot;;  <br><strong>//Default value for service code is `test`.</strong><br>  private static final String SERVICE_CODE = &quot;test&quot;;</pre><pre>public static void main(String[] args) throws InterruptedException <br>{<br>  System.setProperty(&quot;net.spy.log.LoggerImpl&quot;,<br>  &quot;net.spy.memcached.compat.log.SLF4JLogger&quot;);<br>    ArcusClient client =<br>      ArcusClient.createArcusClient(ADDRESS, SERVICE_CODE,<br>      new ConnectionFactoryBuilder());</pre><pre><strong>//Enter key, expiredTime, value of your choice.</strong><br>  client.set(&quot;test:hello&quot;, 30, &quot;Hello Arcus!&quot;);</pre><pre><strong>//Inquiry the saved value using key.</strong><br>  Future&lt;Object&gt; future = client.asyncGet(&quot;test:hello&quot;);<br>  String hello = null;<br>    try {<br>      hello = (String) future.get(700, TimeUnit.MILLISECONDS);<br>    } catch (Exception e) {<br>      future.cancel(true);<br>      } </pre><pre>    if(hello == null) {<br>      hello = &quot;not ok!&quot;;<br>    }<br>    System.out.println(hello);<br> }<br>}</pre><h4>Create logging File</h4><p>Next, we create a log4j2.xml file in the src/main/resources/ directory for logging.</p><pre>&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;<br>&lt;Configuration&gt;<br>  &lt;Appenders&gt;<br>    &lt;Console name=&quot;console&quot; target=&quot;SYSTEM_OUT&quot;&gt;<br>      &lt;PatternLayout pattern=&quot;%d{yyyy-MM-dd HH:mm:ss} [%-5p](%-35c{1}:%-3L) %m%n&quot; /&gt;<br>    &lt;/Console&gt;<br>    &lt;/Appenders&gt;<br>    &lt;Loggers&gt;<br>      &lt;Root level=&quot;WARN&quot;&gt;<br>        &lt;AppenderRef ref=&quot;console&quot; /&gt;<br>      &lt;/Root&gt;<br>    &lt;/Loggers&gt;<br>&lt;/Configuration&gt;</pre><h4>Run</h4><p>Now that all setups are completed, return back to the HelloArcus.java file and run it. If all setups have been completed normally, you should get the following message:</p><pre>Hello Arcus!</pre><p>In case of the following error messages, please check your settings again.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*SNCat8k0eFtsPyroxnFW1w.png" /></figure><p>Up to now, I have shown you how to simply store and lookup data in ARCUS using Java client. There are many more useful features to discover using ARCUS Java Client, such as APIs that utilize collection data structure, asynchronous APIs, etc. that can be useful in the Application. Please refer to ARCUS Java Client documentation(<a href="https://www.jam2in.com/arcus-docs/#/arcus-clients/java-client/1.13/03-key-value-API">URL</a>) for more details.</p><p><strong><em>Note:</em></strong><em> the current ARCUS Documentation is only available in Korean. But you can use </em><a href="https://papago.naver.com/"><em>papago.naver.com</em></a><em> for general translation.</em></p><h3>Conclusion</h3><p>In summary, I have introduced and explained the basic usage of ARCUS Single Cache(Dev.). ARCUS Single Cache(Dev.) was developed as a light version of the ARCUS Cache Cluster to experience the basic features and to ease the use of ARCUS Cache. You can always access it for free up to 750 hours from AWS Marketplace using EC2: t2.micro instance. ARCUS Single Cache(Dev.) is good for small-scale projects to improve performance where caching is needed. In the future, we will provide new ARCUS products with enriching features as an AMI in AWS Marketplace.</p><ul><li>ARCUS Single Cache (service purpose)</li><li>ARCUS Cache Cluster</li></ul><p>▪☞ <a href="https://aws.amazon.com/marketplace/pp/prodview-rbrii3scbuoge?qid=1620957097048&amp;sr=0-1&amp;ref_=srh_res_product_title">AWS Marketplace: ARCUS Single Cache(Dev.)</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9d05067c231d" width="1" height="1" alt=""><hr><p><a href="https://medium.com/jam2in/introducing-arcus-single-cache-dev-on-aws-marketplace-and-how-to-use-it-9d05067c231d">Introducing ARCUS Single Cache (Dev.) on AWS Marketplace and How to Use It</a> was originally published in <a href="https://medium.com/jam2in">JaM2in</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Performance Test of ARCUS Data Persistence]]></title>
            <link>https://medium.com/jam2in/performance-test-of-arcus-data-persistence-516778a90379?source=rss----c55eb3541815---4</link>
            <guid isPermaLink="false">https://medium.com/p/516778a90379</guid>
            <category><![CDATA[cache]]></category>
            <category><![CDATA[data]]></category>
            <category><![CDATA[jam2in]]></category>
            <category><![CDATA[persistence]]></category>
            <category><![CDATA[arcus-cache-cluster]]></category>
            <dc:creator><![CDATA[N.M.G.]]></dc:creator>
            <pubDate>Wed, 16 Jun 2021 03:40:30 GMT</pubDate>
            <atom:updated>2021-06-16T10:04:54.601Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/393/1*rydjsueevQTeDhd-gTZNzw.png" /></figure><p>In the previous blog, <a href="https://medium.com/jam2in/overview-and-usage-of-persistence-feature-to-preserve-data-permanently-in-arcus-cache-system-5f77d29c69b">Overview and Usage of Persistence Feature to Preserve Data Permanently in ARCUS Cache System</a>, I have introduced ARCUS Persistence and showed you essential ways to send requests to it with telnet and memtier_benchmark tools. As I have mentioned in the previous blog, ARCUS Persistence has been implemented to guarantee high performance by minimizing the overhead of a data persistence feature so that there would be no significant difference from the performance of ARCUS Cache. In this article, we will compare the performances of the ARCUS Persistence to the original ARCUS caching and show the results of performance differences according to the <em>Command Logging mode.</em></p><h4>Test Environment</h4><p>Performance test of ARCUS Persistence will be measured in accordance with establishing the following environmental requirements.</p><h4>Test Machine Specs</h4><p>Below listed specifications of a <strong>system</strong> on which the ARCUS Cache node runs. In this performance test, we have used only a single ARCUS node.</p><blockquote>OS : CentOS 7.3.1611<br>CPU : 8vCPU<br>MEMORY : 8GB * 2<br>NETWORK : 1Gbps<br>DISK : HDD 50GB * 2<br>ETC :<br><em>▪</em> THP(Transparent Huge Pages) = madvise<br><em>▪</em> vm.swapiness = 30</blockquote><p>Below listed specifications of a <strong>system</strong> on which the client that generates the load runs.</p><blockquote>OS : CentOS 7.3.1611<br>CPU : 8vCPU<br>MEMORY : 8GB * 2<br>NETWORK : 1Gbps<br>DISK : SSD 50GB</blockquote><h4>ARCUS Server Running Options</h4><p>The ARCUS Cache node that we have use runs on <em>arcus-memcached</em> 1.13.1 and a start command is as follows:</p><pre>memcached -d -v -r -R 100 -t 6 -p 11500 -b 8192 -c 4096 -m 12000 \<br>-z localhost:2150 \<br>-X /home/test/arcus/lib/syslog_logger.so \<br>-E /home/test/arcus/lib/default_engine.so \<br>-e config_file=/home/test/arcus/conf/default_engine.conf</pre><p>Considering the client load, the below-shown running options must be set properly.</p><blockquote>t : number of worker threads<br>R : maximum number of requests per event<br>b : TCP backlog queue size<br>c : maximum number of clients that can be connected<br>m : maximum storage capacity (MB)</blockquote><h4>ARCUS Persistence Settings</h4><p>In the default_engine.conf file that has the settings for ARCUS default engine operation, Persistence related settings also have been set as shown in the below sample. You can check the details of the Persistence setting in the <a href="https://medium.com/jam2in/overview-and-usage-of-persistence-feature-to-preserve-data-permanently-in-arcus-cache-system-5f77d29c69b">Overview and Usage of Persistence Feature to Preserve Data Permanently in ARCUS Cache System</a>. Therefore, here I will only show you the values to be set according to each test case.</p><pre># Persistence configuration<br>#<br># use persistence (true or false, default: false)<br>use_persistence=true<br>#<br># The path of the snapshot file (default: ARCUS-DB)<br>data_path=/disk2/test/arcus/ARCUS-DB<br>#<br># The path of the command log file (default: ARCUS-DB)<br>logs_path=/home/test/arcus/ARCUS-DB<br>#<br># asynchronous logging<br>async_logging=false<br>#<br># checkpoint interval (unit: percentage, default: 100)<br># The ratio of the command log file size to the snapshot file size.<br># 100 means checkpoint if snapshot file size is 10GB, command log <br># file size is 20GB or more<br>chkpt_interval_pct_snapshot=100<br>#<br># checkpoint interval minimum file size (unit: MB, default: 256)<br>chkpt_interval_min_logsize=256</pre><ul><li>use_persistence<br>▪ Set to true if you test ARCUS for storage purposes and if you test ARCUS for cache purposes set it to false.</li><li>data_path, logs_path<br>▪<strong> We recommend that you set the data file and the log file paths to</strong> <strong>separate disk partition.</strong><br>▪ This is because the snapshot of the checkpoint spends all IO resources of a disk, thus delaying command logging by worker threads that process client requests.</li><li>async_logging<br>▪ Set true for asynchronous logging test, false for synchronous logging test.</li><li>chkpt_interval_pct_snapshot, chkpt_interval_min_logsize<br>▪ We used a default value.</li></ul><h4>Performance Measurement Tools</h4><p>We used memtier_benchmark <strong>1.3.0</strong> version to generate loads and measure performance. By supporting Memcached protocols we can perform insert (set) and retrieval (get) operations of the ARCUS KV commands. You can also generate keys in a uniform or Gaussian distribution and check the average throughput per second and the average/tail response time.</p><h4>ARCUS Monitoring Tools</h4><p>Hubble is the ARCUS Enterprise monitoring tool. Hubble collects the statistical information of an ARCUS instance and system resources of the host machine and shows them visually in a web browser. So we can easily observe ARCUS and system status through Hubble.</p><h3>Performance Test Scenario</h3><h4>Comparison of Test Items</h4><p>Performance variables to be checked and compared in this test.</p><ul><li>Throughput - Average throughput</li><li>Latency - Average response time, a tail response time (90%, 95%, 99%)</li></ul><h4>Generate test data</h4><p>Test data has been automatically generated by the memtier_benchmark.</p><blockquote><strong>Total data count: 50 million (6.5GB)</strong><br>▪ Key size: 9~17Bytes (“memtier-1” ~ “memtier-50000000”)<br>▪ Data size : 50 Bytes<br>▪ Item size : Average 130 Bytes (total key, data, metadata)</blockquote><h4>Test Scenario and Procedure</h4><p>For the performance test below, we will be measuring the performance of a cache mode, asynchronous logging mode, and synchronous logging mode with the memtier_benchmark. In these <em>retrieval</em> and <em>update</em> performance tests, we will generate keys only in a <strong><em>uniform distribution</em></strong><em>. </em>The reason is in ARCUS entire data resides in memory, hence both uniform and Gaussian distributions will show the same performance results.</p><blockquote><strong>Performance Test of Insert</strong><br>▪ Insert 50 million data,<br>▪ Execution command:</blockquote><pre>memtier_benchmark --threads=8 --clients=50 --data-size=50 \<br>--key-pattern=P:P --key-minimum=1 --key-maximum=50000000 \<br>--ratio=1:0 --requests=125000 --print-percentiles=90,95,99</pre><blockquote><strong>Performance test of Retrieval</strong><br>▪ After inserting 50 million data, generate keys in a uniform distribution and retrieve.<br>▪ Execution command</blockquote><pre>memtier_benchmark --threads=8 --clients=50 --data-size=50 \<br>--key-pattern=R:R --key-minimum=1 --key-maximum=50000000 \<br>--distinct-client-seed --randomize --ratio=0:1 --requests=125000 \<br>--print-percentiles=90,95,99</pre><blockquote><strong>Performance tests of Mixed Update and Retrieval</strong><br>▪ After inserting 50 million data, generate keys in a uniform distribution. Insert and retieve at the ratio of 1:9, 3:7, and 5:5.<br>▪ Execution command</blockquote><pre>memtier_benchmark --threads=8 --clients=50 --data-size=50 \<br>--key-pattern=R:R --key-minimum=1 --key-maximum=50000000 \<br>--distinct-client-seed --randomize --ratio=1:9 --requests=125000 \<br>--print-percentiles=90,95,99</pre><h4>Performance Test Results</h4><p>Average throughput and average/tail response time have been organized according to persistence mode.</p><ul><li>OFF is original cache performance,</li><li>ASYNC is asynchronous command logging mode,</li><li>SYNC is synchronous command logging mode.</li></ul><h4>Insert Operation</h4><p>The performance test result of 50 million data insert operation is as follows:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1020/1*FsJ1d-kmSs0fInWplSRVlQ.png" /></figure><p>The ARCUS Persistence performance was distinctly shown since all client requests consist of the insert operation. The result shows that the performance degradation associated with the persistence usage is not that great even compared to the original cache performance when it’s not used. Especially, synchronous logging mode completes <em>update operation’s</em> execution after verifying that worker threads recorded a command log onto a disk so that data can be fully recovered even if the ARCUS instance terminated abnormally at any point in time. The reason for such a high-performance result is that the worker threads have been implemented in a way that they can handle requests from other clients during the command log is being flushed to a disk by flush daemon thread.</p><h4>Retrieval Operation</h4><p>The performance test result of retrieving 50 million data in uniform distribution is as follows:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1020/1*NCdg3IhvW9mI3MLO45-Xug.png" /></figure><p>In the case of retrieval operations, command logging wasn’t executed, because there wasn’t any insert request. Thus, even if persistence mode would be used it will have the same performance results as an ARCUS cache.</p><h4>Mixed Operation</h4><p>The performance test result of mixed Update and Retrieval at a ratio of 1:9 is as follows:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1020/1*BAO30Z6hjggJ8gCpr_xtQg.png" /></figure><p>The performance test result of mixed Update and Retrieval at a ratio of 3:7 is as follows:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1020/1*D7yuYmJlwT5WLK6_OHAYCg.png" /></figure><p>The performance test result of mixed of Update and Retrieval at a ratio of 5:5 is as follows:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1020/1*2PFlauPs4qlj8xLAc_dwFA.png" /></figure><p>As you can see, even in the mixed operation’s performance there are no big differences with the persistence usage. Especially, because of the nature of the ARCUS system implementation, it has shown very good results on the performance of retrieval operation. The higher the volume of retrieval requests, the higher the performance regardless of whether the persistence mode is used or not. In the case of Update and Retrieval at a ratio of 1:9, the request throughput of synchronous logging mode(SYNC) differs only about 20K from the request throughput of the original ARCUS Cache(OFF).</p><h4>Checkpoint Impact</h4><p>Additionally, to verify the changes in request throughput of ARCUS instance during a checkpoint, we’ll show you the User/System CPU usage of an ARCUS host machine and the request throughput of ARCUS instance observed by Hubble.</p><blockquote>P.S. Besides the below shown information, Hubble provides many other stats (network, disk, operation hit/miss, etc.) for analysis. But for now, we leave them out.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*tuAhoSaIngA-8Q7oXU3Sew.png" /></figure><p>The above image shows the result of the performance test of a mixed operation of Update and Retrieval at a ratio of 1:9, operating in asynchronous logging mode with 50 million data insertion. The average throughput is about 150K combined with a throughput of update(yellow) and a throughput of retrieval(green). The time between 16:21 and 16:23 shows the checkpoint execution period. During that time throughput is temporarily reduced, and about 5GB was recorded to the snapshot file.<strong> As a future plan, we’re planning to slow down the checkpoint process further reducing the throughput degradation.</strong></p><h3>Conclusion</h3><p>In this blog article, we have measured the performance difference of ARCUS Persistence according to command logging mode and the ARCUS Cache. As mentioned earlier, ARCUS Persistence was implemented to minimize the impact on the process of client requests, thus there isn’t any significant performance difference from the ARCUS cache. <strong>Especially, most real service workload patterns are a mixture of insert and retrieval operations, that have a high volume of retrieves, and our mixed operation test results showed a notably higher performance. </strong>The disk of this test environment is a defaultHDD provided by Naver Cloud Platform’s VM. If you use the NVMe SSD that provides high IOPs you can obtain a higher ARCUS performance. ARCUS Persistence is a good choice for most applications to ensure high performance where data preservation is required and smooth serviceability. We are continuously working on ARCUS Persistence optimization, trying to improve throughput, response time, and ease of use from an operational perspective.</p><p>Lastly, as a precaution for a use of ARCUS Persistence, because a checkpoint operation of ARCUS Persistence records the entire data from a memory to a disk, many disk IO resources will be used, which may delay command logging for the update operation. <strong>Therefore, disk partitioning between data files and log files is essential, in order to obtain high performance of ARCUS Persistence.</strong></p><p>Previous blog posts:<br>▪☞ <a href="https://medium.com/jam2in/overview-and-usage-of-persistence-feature-to-preserve-data-permanently-in-arcus-cache-system-5f77d29c69b">Overview and Usage of Persistence Feature to Preserve Data Permanently in ARCUS Cache System</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=516778a90379" width="1" height="1" alt=""><hr><p><a href="https://medium.com/jam2in/performance-test-of-arcus-data-persistence-516778a90379">Performance Test of ARCUS Data Persistence</a> was originally published in <a href="https://medium.com/jam2in">JaM2in</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Dynamically Manage and Update Cache Target API Lists of ARCUS Application]]></title>
            <link>https://medium.com/jam2in/dynamically-manage-and-update-cache-target-api-lists-of-arcus-application-b5db919fe1e2?source=rss----c55eb3541815---4</link>
            <guid isPermaLink="false">https://medium.com/p/b5db919fe1e2</guid>
            <category><![CDATA[zookeeper]]></category>
            <category><![CDATA[arcus-cache-cluster]]></category>
            <category><![CDATA[jam2in]]></category>
            <category><![CDATA[api]]></category>
            <category><![CDATA[java]]></category>
            <dc:creator><![CDATA[N.M.G.]]></dc:creator>
            <pubDate>Fri, 21 May 2021 10:46:04 GMT</pubDate>
            <atom:updated>2021-05-21T10:46:03.967Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/428/1*TofggKtq8EGVvYHD7RHgdg.png" /></figure><p>The ARCUS Common Module makes use of Spring AOP technology to easily apply ARCUS Cache in Java Applications. There are two ways to apply ARCUS Cache, (1) attach Annotation to the cache target API and (2) specify cache target APIs and their cache attributes in the Property file. More details on the ARCUS Common Module are described in the <a href="https://medium.com/jam2in/arcus-common-cache-module-with-basic-pattern-caching-in-java-environment-db88c4bf7585">ARCUS Common Cache Module Use with Basic Pattern Caching in Java Environment</a> article.</p><p>When updates of cache target API or cache attributes are required from the caching methods (Annotation method, Property file method) of ARCUS Common Module, Application redeployment was needed to modify and reflect these changes. Due to the need to make updates without Application redeployment, the dynamic Property management method was developed.</p><p>We have used ZooKeeper as ARCUS metadata storage, to keep the cache target API data(cache target API + cache attributes) and have made it possible to update them when it’s necessary. ARCUS Common Module detects and retrieves the real-time changes of cache target APIs with the ZooKeeper’s Watcher feature and dynamically applies the changes to ARCUS applications.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6UjBf97tXxpdinKGNWgMtQ.png" /></figure><h3>Management of Cache Target API Data in ZooKeeper</h3><p>ZooKeeper has a directory structure of key-value items called znode (ZooKeeper Node) that stores and manages desired data in it. Below-shown ZooKeeper’s directory structure describes how to store and manage cache target API data of the ARCUS Application. At the top of the structure, there come /arcus_app and /cache_target_list directories followed by /service-code/ znodes of ARCUS Cache Cluster. The cache target API list and their attributes of each ARCUS Application are stored to sub znodes of the service code that the Application access.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*PRrmXwBqeX3JNda_Ftd0Ww.png" /><figcaption>ZooKeeper directory structure</figcaption></figure><p>The key of the znode can solely identify the cache target API by using a target name consisting of the ‘package-name.class-name.method-name’ and the value of the znode stores the JSON property as shown in the below example.</p><pre>/* <br><strong>JSON Property of Cache Target<br></strong>*/<br>{</pre><pre>/*<br><strong>It is consisting of </strong><strong>‘package-name.class-name.method-name’ as a</strong> <strong>signature of Cache Target API</strong><strong>. </strong><br>*/</pre><pre><strong>  &quot;</strong>target&quot;:&quot;com.service.BoardService.getBoard&quot;,</pre><pre>/*<br><strong>Assigns the ARCUS Cache Key&#39;s prefix generated from Cache Target API.</strong><br>*/ </pre><pre>  &quot;prefix&quot;:&quot;BOARD&quot;,</pre><pre>/*<br><strong>Assigns the ARCUS Cache Item&#39;s TTL (Time to Live) generated from Cache Target API.<br></strong>*/<strong> </strong></pre><pre><strong>  </strong>&quot;expireTime&quot;:60,</pre><pre>/*<br><strong>Specifies the key parameters (separated by commas) that will be used as ARCUS Cache Key generated from Cache Target API. </strong><br>*/</pre><pre>  &quot;keyParams&quot;:[&quot;bno&quot;],</pre><pre>/*<br><strong>Automatic creation of ARCUS Cache Keys generated from Cache Target API: if true automatically create a cache key using all parameters; if false keyParams must be specified.</strong><br>*/</pre><pre>  &quot;keyAutoGeneration&quot;:false,</pre><pre>/*<br><strong>Condition of whether to apply Arcus Cache on the Cache Target API.<br></strong>*/</pre><pre>  &quot;enable&quot;:true,</pre><pre>/*<br><strong>Append the Arcus Cache Key creation time information after the Arcus Cache Key string. This is intended for to create different cache keys.</strong><br>*/<br>  &quot;keyDate&quot;:&quot;KEY_DATE_NONE&quot;</pre><pre>}</pre><p>All arcus_apps, cache_target_list and service_code znodes that store cache target APIs are all generated in <strong>persistent type</strong> and the znodes that store the cache target APIs data are created in <strong>persistent and sequence types</strong>. The reason why we use the <strong>sequence type</strong> is, to easily determine creation, deletion, and other cache target API property updates. The cache target API property Update operation removes the existing znode and generates a new znode of the same key with updated property details. Thereby causes the corresponding znode’s sequence value to be increased.</p><p>For instance, let’s say when we want to update API’s cache property where the znode of com.service.BoardService.getBoard-0000000000 key already exists. On the update operation, an existing znode will be removed, and a new znode with com.service.BoardService.getBoard-0000000001 key will be created at the same time. Thereby, the ARCUS Application looks up only the child nodes in /arcus_apps/cache_target_list/service-code znode and allows you to check creation, deletion, and update with the <strong>sequence value</strong> in its own cache target API list.</p><h3>The ARCUS’s Cache Common Module Lookups the Latest Cache Target API List from ZooKeeper (using ZooKeeper Watcher)</h3><p>ARCUS Common Module uses a <strong><em>cache item map</em></strong> to store and manage the cache target API list of the corresponding service code which the Application access.</p><p>In order to receive real-time notifications, we register the ZooKeeper Watcher to the corresponding znode of service code. When the changes occur in the corresponding cache target API list, ZooKeeper Watcher notifies any occurred updates to ARCUS Common Module. Then, ARCUS Common Module reads the latest cache target API list. It looks up the updated cache target API list by comparing the <em>sequence values</em> of previous and latest cache target APIs. ARCUS Common Module also reads the property details of the updated cache target API as a JSON file from ZooKeeper and updates them on the cache item map.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*V6SiLy0EUFzrfoStlzwKsg.png" /></figure><h3>Creation, Deletion, and Update of Cache Target API Properties</h3><p>By directly using the ZooKeeper Command Line Interface tool — zkCli, you can create, delete and update cache target API property details as shown below. In the below example, the -s option is given to create a znode of <em>sequence type</em>. But it would be much efficient to make and use a script that executes the corresponding operations than operating directly with a zkCli tool.</p><pre><strong>// CREATION of Cache Target API</strong><br>[ZK: localhost:2180(CONNECTED)]<br>create -s /arcus_apps/cache_target_list/service-code/com.service.BoardService.getBoard <br>{<br> &quot;target&quot;:&quot;com.service.BoardService.getBoard&quot;,<br> &quot;prefix&quot;:&quot;BOARD&quot;,<br> &quot;expireTime&quot;:60,<br> &quot;keyParams&quot;:[&quot;bno&quot;],<br> &quot;keyAutoGeneration&quot;:false,<br> &quot;enable&quot;:true,<br> &quot;keyDate&quot;:&quot;KEY_DATE_NONE&quot;<br>}<br><br><strong>// DELETION of Cache Target API</strong><br>[ZK: localhost:2180(CONNECTED)]<br>delete /arcus_apps/cache_target_list/service-code/com.service.BoardService.getBoard-0000000000<br><br><strong>// UPDATE of Cache Target API</strong><br>[ZK: localhost:2180(CONNECTED)]<br>create -s /arcus_apps/cache_target_list/service-code/com.service.BoardService.getBoard<br>{<br> &quot;target&quot;:&quot;com.service.BoardService.getBoard&quot;,<br> &quot;prefix&quot;:&quot;BOARD&quot;,<br> &quot;expireTime&quot;:60,<br> &quot;keyParams&quot;:[&quot;bno&quot;],<br> &quot;keyAutoGeneration&quot;:false,<br> &quot;enable&quot;:true,<br> &quot;keyDate&quot;:&quot;KEY_DATE_NONE&quot;<br>}<br>[ZK: localhost:2180(CONNECTED)]<br>delete /arcus_apps/cache_target_list/service-code/com.service.BoardService.getBoard-0000000000</pre><p>Performing creation, deletion, and update operations of cache target API directly using a zkCli tool can be uncomfortable for an operator. On the webpage, to efficiently operate and manage ARCUS Cache Cluster (including ZooKeeper Ensemble) <strong>JaM2in</strong> currently developing<strong> <em>ARCUS</em> <em>Admin Tool</em></strong>. On the ARCUS Admin Tool, we have developed a cache target feature to manage the Application’s cache target APIs, as shown in the below example.</p><p>Below web page shows the cache targe API list of a specific service code. In this page, our service code’s name is a test, by clicking on a particularcache target API, you can check the details of JSON property.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kCvgv8DBlC296Gbtb3b3kA.png" /></figure><p>The below image displays how to create a new cache target API. A user enters the property details that will be made as a JSON property and creates the corresponding cache target API. Relatively, delete and update operations of cache target API can be performed in the same manner on the ARCUS Admin Tool.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2FWTUxGktDF73ed9imldbQ.png" /></figure><p>To recap briefly what has been discussed so far, when a user creates, deletes, or updates the cache target APIs through cache target feature on the ARCUS Admin Tool, these changes are stored and managed by ZooKeeper Ensemble, where ZooKeeper Watcher monitors these change events and passes them to the ARCUS Common Module which is attached to the Application. As explained earlier, ARCUS Common Module looks up for updates on cache target API and manages them with a cache item map. ARCUS Cache will be automatically applied only to cache target APIs that stored on the cache item map.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*o9ZWcGoj7RrZtb72KTQwTA.png" /></figure><h3>Application Method of the ARCUS Common Module</h3><p>ARCUS Common Module itself is described in the <a href="https://medium.com/jam2in/arcus-common-cache-module-with-basic-pattern-caching-in-java-environment-db88c4bf7585">ARCUS Common Cache Module Use with Basic Pattern Caching in Java Environment</a> article, but here as well, I will give you a brief introduction on how to apply the ARCUS Common Module to the Java Application.</p><p>In order to apply ARCUS Common Module to the Java Application first, add the ARCUS Common Module dependency to the pom.xml file of the Java Application.</p><pre>&lt;dependencies&gt;<br>   ...<br>   &lt;dependency&gt;<br>      &lt;groupId&gt;com.jam2in.arcus&lt;/groupId&gt;<br>      &lt;artifactId&gt;arcus-app-common&lt;/artifactId&gt;<br>      &lt;version&gt;1.4.0&lt;/version&gt;<br>   &lt;/dependency&gt;<br>   ...<br>&lt;/dependencies&gt;</pre><p>Then, create the ARCUS property file (arcus.properties) as shown below.</p><pre><strong># ZooKeeper Ensemble Address <br># Used to Connect  ARCUS and Renew Cache Target List </strong><br>arcus.address=1.2.3.4:2181,1.2.3.4:2182,1.2.3.4:2183<br><br><strong># Arcus service code</strong><br>arcus.serviceCode=test<br><br><strong># Connection Pool size of Arcus Client </strong><br>arcus.poolSize=8<br><br><strong># Operation timeout (milliseconds)</strong><br>arcus.asyncOperationTimeout=700<br><br><strong># Global Prefix of Arcus Cache Key </strong><br>arcus.globalPrefix=RELEASE</pre><p>Lastly, if you set up the Spring as shown below, the ARCUS Common Module application will be completed. The ARCUS Common Module makes it very easy for a user to apply ARCUS Cache and dynamically manage a cache target API list.</p><pre>@Configuration<br><strong>// Arcus Property Loading</strong><br>@PropertySource(&quot;classpath:arcus.properties&quot;)<br><strong>// @Aspect Use</strong><br>@EnableAspectJAutoProxy(proxyTargetClass = true)<br>@Import(ArcusBeans.class)<br>public class ArcusConfiguration { <br> @Autowired<br> private ArcusBeans arcusBeans;<br> <br> @Bean<br> public static PropertySourcesPlaceholderConfigurer <br>          propertySourcesPlaceholderConfigurer() {<br>  return new PropertySourcesPlaceholderConfigurer();<br> }<br> <br> @Bean<br> public ArcusStarter arcusStarter() {<br>  return new ArcusStarter(arcusBeans);<br> } <br>   <br> @Bean<br> public ArcusCacheAspect arcusAdvice() {<br>  return new ArcusCacheAspect(arcusBeans);<br> }<br>}</pre><h3>Conclusion</h3><p>In summary, I’ve introduced and explained how to dynamically create, delete and update cache target APIs without restart of the ARCUS Application, and how to store and manage cache target APIs using ZooKeeper. ARCUS Common Module through ZooKeeper Watcher detects real-time updates in the cache target API list and always maintains the latest updated list of cache target APIs. In the future, to improve the management feature of a cache target API, the following works will be processed:</p><ul><li>HIT, MISS, RATIO indication of cache target API,</li><li>HISTORY feature of cache target API e.g. records of by whom and when the cache target API property details were updated,</li><li>A parser feature to view cache target API as a JSON string or JSON file.</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b5db919fe1e2" width="1" height="1" alt=""><hr><p><a href="https://medium.com/jam2in/dynamically-manage-and-update-cache-target-api-lists-of-arcus-application-b5db919fe1e2">Dynamically Manage and Update Cache Target API Lists of ARCUS Application</a> was originally published in <a href="https://medium.com/jam2in">JaM2in</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>