Building Microsofts contender for a Redis (Garnet) on the Raspberry Pi
Published: 2024-03-25 03:18:05

When finding out about Redis going from a BSD to a dual license, I felt a bit betrayed. I was still a holdout of using Redis locally and became a huge fan of its speed and support. While there were competitors on the market, such as KeyDB and DragonFly DB, Key had no native Pi abilities that I could tell, and DragonFly also had a Business License over an open-source license. I used Redis because not only was it the top name in a quick in-memory database, but because it was open source, meaning there was a community of engineers and businesses willing to support the product.  

I just, like 2 hours ago, found out about Microsofts recent open source offering, and had to give it a go. With a larger name backing the product, and being more of a "dot net" shop in my house, I wanted to see what this product could do, and see if it was a decent drop-in replacement for my Redis server, which I had just ported to my Raspberry Pi 5

I don't see myself playing with this too much tonight, I did want to post at least my Dockerfile that I used to get this running

I am curious though, and could use some help... I don't know exactly where the files are stored for the Tsavorite DB, as I'd like to keep that on a volume for rebuilds and upgrades... I'm sure it's buried somewhere in the documentation.



So... without further ado... Here is the dockerfile I used... it is slightly tweaked from what Microsoft has in the repo but doesn't require a full download of the git repo to build. I also specifically targeted the arm64-based image instead of the x64. I also am using a chiseled version of the runtime, to help reduce surface attack area, and have a smaller overall image. The final image output appears to be 101 MB in size, which isn't too bad, but still more than double that of the official 7.2.4-alpine image (41.6 MB) I'm using from Redis

```
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /source

# Copy files
RUN curl -L https://github.com/microsoft/garnet/archive/refs/tags/v1.0.1.tar.gz -o garnet.tar.gz \
    && tar -xzf garnet.tar.gz --strip-components=1 \
    && rm garnet.tar.gz

RUN dotnet restore
RUN dotnet build -c Release

# Copy and publish app and libraries
WORKDIR /source/main/GarnetServer
RUN dotnet publish -c Release -o /app -r linux-arm64 --self-contained false -f net8.0

# Final stage/image
# FROM mcr.microsoft.com/dotnet/runtime:8.0
FROM mcr.microsoft.com/dotnet/runtime:8.0-jammy-chiseled
WORKDIR /app
COPY --from=build /app .

# Run GarnetServer with an index size of 128MB
ENTRYPOINT ["/app/GarnetServer", "-i", "128m"]
```


For a Docker-compose file, I went with something simple

```
version: '3.3'
services:
  garnet:
    restart: always
    build:
      context: .
    image: garnet
    container_name: garnet
    ports:
      - "6379:3278"
    volumes:
      - /etc/localtime:/etc/localtime:ro
```
I map the port of entry for Garnet to the same port Redis uses. Let me know if this helps you, or if you have any recommendations! 

Side... as I play with this more, I'll be sure to update this post with my findings. My current use cases are using the Pub/Sub functionality, as well as a simple key-value memory cache for my services. 


## updates 2023-04-05

So.. after getting some info back from github, I was able to get my docker instance to persist. so I have some updates to my files... for starters, I migrated my configs out of the entry point, and into a file. This way, I could keep persistence on the data I have. There is some cleanup work or optimizations I'd like to do, but I might reserve that for when I try out ValKey... the Linux Foundations version of Redis they are developing. 

Here is what my dockerfile now looks like.. if you're running this on an Intel or AMD based machine, you'll want to drop the -r linux-arm64 from the publish command

```
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /source

RUN curl -L https://github.com/microsoft/garnet/archive/refs/tags/v1.0.2.tar.gz -o garnet.tar.gz \
    && tar -xzf garnet.tar.gz --strip-components=1 \
    && rm garnet.tar.gz

RUN dotnet restore
RUN dotnet build -c Release

# Copy and publish app and libraries
WORKDIR /source/main/GarnetServer
RUN dotnet publish -c Release -o /app -r linux-arm64 --self-contained false -f net8.0

# Final stage/image
FROM mcr.microsoft.com/dotnet/runtime:8.0
WORKDIR /app
COPY --from=build /app .
COPY garnet.config /app/garnet.conf
VOLUME /data
RUN mkdir -p /data/checkpoint /data/logs
ENTRYPOINT ["/app/GarnetServer", "-i", "128m", "--config-import-path", "/app/garnet.conf"]
```

my docker-compose.yml has also slightly changed
```
version: '3.3'
services:
  garnet:
    restart: always
    build:
      context: .
    image: garnet
    container_name: garnetpersist
    ports:
      - "6399:3278"
    volumes:
      - ./data:/data
```
as noted below, I dropped the time syncing between the container and system time in this container, however, I do have a data directory in the same directory as the volume... I map my folders in docker, but if you want to use proper "volumes", it should be a hard change to invoke.


For my config, to save persistence, I am using the following config file, which is pulled into my image at the time of compile. It wouldn't also be that hard to map, but in my simple case, I just pull it in from the dockerfile build script

garnet.config
```
{
  "MemorySize": "128m",
  "Port" : 3278,
  "Address": "0.0.0.0",
  "CheckpointDir": "/data/checkpoint",
  "EnableAOF": true,
  "Recover": true
}
```
It was also recommended to me that I call SAVE or BGSAVE then data will be checkpointed... this way I'm not running every write operation to the system through AOF. I have to thank Badrish Chandramouli and kingcomxu on Github for the recommendations! here is a [link](https://github.com/microsoft/garnet/discussions/166) to our discussion of it on Github