<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Andrey Alekseev</title>
    <description>The latest articles on Forem by Andrey Alekseev (@ikintosh).</description>
    <link>https://forem.com/ikintosh</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F403406%2F1df4ad26-2530-48ab-a0ca-04d061269728.jpeg</url>
      <title>Forem: Andrey Alekseev</title>
      <link>https://forem.com/ikintosh</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/ikintosh"/>
    <language>en</language>
    <item>
      <title>Doing DE job as DS</title>
      <dc:creator>Andrey Alekseev</dc:creator>
      <pubDate>Fri, 14 Mar 2025 07:30:00 +0000</pubDate>
      <link>https://forem.com/ikintosh/doing-de-job-as-ds-4h93</link>
      <guid>https://forem.com/ikintosh/doing-de-job-as-ds-4h93</guid>
      <description>&lt;p&gt;As a Machine Learning Engineer I've seen how data pipelines become crucial for effective ML systems (even more than the models itself). So instead of refusing this job, I suggest to embrace it. In a daily DS job, you inevitably end up doing DE work. Creating data pipelines to move data between systems isn't just a one-time thing, and data rarely cooperates by fitting neatly in memory. After the model development phase, you're stuck in the pipeline maintenance period.&lt;/p&gt;

&lt;p&gt;So what are our options? I've tried a few approaches...&lt;/p&gt;




&lt;h2&gt;
  
  
  Python (Micro)service (FastAPI or CronJob in container)
&lt;/h2&gt;

&lt;p&gt;Works nicely for all types of data. With &lt;a href="https://github.com/dbader/schedule" rel="noopener noreferrer"&gt;schedule package&lt;/a&gt; that's quite easy to set up for regular jobs and with FastAPI you can even go for streaming data. Most pipelines run hourly or daily anyway. I would not recommend doing it for big(-ger) data though. Sure, you can process in batches, but now you're writing that orchestration logic yourself. Plus if you ever need to speed up the process you are kinda left without scaling ability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scheduled Jobs (Cloud Run, K8s Jobs and CronJobs)
&lt;/h2&gt;

&lt;p&gt;This has become my go-to for medium datasets (especially with cloud run ease of setup). Being just a Docker container means I can use whatever packages and architecture I want. Startup times are quick, and it can be manually scaled. &lt;/p&gt;

&lt;p&gt;K8s Jobs and Cloud Run Jobs both let you parameterize executions. Batch numbers, date ranges, or partition keys are parameters that can be passed to the job without rebuilding the docker image. Here you only need to handle starting up the job. That can be done via Airflow or any microservice that can send http requests or execute kubectl commands. With CronJobs you can simply set up multiple cronjobs with different parameters and the startup will be handled by k8s itself. Another upside is that you pay only for uptime. However there is an annoying part as well. You build everything by hand: integration, batching, orchestration, failure scenarios. &lt;/p&gt;

&lt;h2&gt;
  
  
  Specialized Services (Apache Beam with Dataflow, Spark, Flink)
&lt;/h2&gt;

&lt;p&gt;Apache Beam allows to describe your workload and then execute it on different runners. If you have these runners available then you are in luck because Beam and its runners come with a lot of pre-built connections for pushing data around and handle batching, autoscaling and throttling out of the box. It works with streaming data too. But Apache Beam is more expensive and has a steeper learning curve even with runners available. But as a DS you will love tweaking the configs to find the perfect batch size interval. This system works really good after you spend time setting it up and configuring and have all necessary components nearby to create new or tweak existing pipeline.&lt;/p&gt;

&lt;p&gt;Despite these options, consistency triumphs theoretical perfection. If your team has invested in Apache Beam, stick with it rather than introducing yet another pipeline approach.&lt;/p&gt;

&lt;p&gt;Anyway these DE skills aren't optional extras anymore - they're becoming core to our toolkit. The data scientists are expected to manage the full DS lifecycle.&lt;/p&gt;

&lt;p&gt;What approaches have worked for you?&lt;/p&gt;

</description>
      <category>dataengineering</category>
      <category>datascience</category>
      <category>machinelearning</category>
      <category>programming</category>
    </item>
    <item>
      <title>🚀 Why Your ML Service Needs Rust + CatBoost: A Setup Guide That Actually Works</title>
      <dc:creator>Andrey Alekseev</dc:creator>
      <pubDate>Sun, 19 Jan 2025 19:05:37 +0000</pubDate>
      <link>https://forem.com/ikintosh/why-your-ml-service-needs-rust-catboost-a-setup-guide-that-actually-works-1k76</link>
      <guid>https://forem.com/ikintosh/why-your-ml-service-needs-rust-catboost-a-setup-guide-that-actually-works-1k76</guid>
      <description>&lt;p&gt;Let’s talk about a problem that’s been bothering ML teams for a while now. When we’re working with batch processing, Python does the job just fine. But real-time serving? That’s where things get interesting.&lt;/p&gt;




&lt;p&gt;I ran into this myself when building a service that needed to stay under 50ms latency. Even without strict latency requirements, everything might seem fine at first, but as the service grow, Python starts showing its limitations. Variables would randomly turn into None, and without type checks, tracking down these issues becomes a real headache.&lt;/p&gt;

&lt;p&gt;Right now, we don’t have a go-to solution for real-time model serving. Teams often turn to tools like KServe or BentoML, but this means dealing with more moving parts — more pods to watch, extra network calls slowing things down.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsaterobo15lwo315txmy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsaterobo15lwo315txmy.png" alt="Possible ways of serving the model" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What about other languages? C++ is fast and works with pretty much every ML library out there, but let’s be real — building and maintaining a C++ backend service is not something most teams want to take on.&lt;/p&gt;

&lt;p&gt;It would be great if we could build models in Python but serve them in a faster language. ONNX tries to solve this, and it works relatively great for neural networks. But when I tried using it with CatBoost, handling categorical features turned into a challenge — the support just isn’t there yet.&lt;/p&gt;

&lt;p&gt;This brings us to Rust, which offers an interesting middle ground:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It’s just as fast as C++, but easier to my liking&lt;/li&gt;
&lt;li&gt;The type system keeps your business logic clean and predictable&lt;/li&gt;
&lt;li&gt;The compiler actually helps you write better code instead of just pointing out errors&lt;/li&gt;
&lt;li&gt;The ML ecosystem is growing, with support from big names like Microsoft&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Working with Official Catboost in Rust
&lt;/h1&gt;

&lt;p&gt;Good news — there’s actually an official Catboost crate for Rust! But before you get too excited, let me tell you about quirks that I discovered along the way.&lt;/p&gt;

&lt;p&gt;The tricky part isn’t the Rust code itself — it’s getting the underlying C++ libraries in place. You’ll need to compile Catboost from source, and getting the environment right for this is most difficult part.&lt;/p&gt;

&lt;p&gt;Catboost team provides their own Ubuntu-based image for building it from source, which sounds great. But what if you’re planning to run your service on Debian to keep things light? Then you better build Catboost on the same version of Debian you’ll use for serving, otherwise you might run into compatibility issues.&lt;/p&gt;

&lt;p&gt;Let’s talk about why this matters in practice. The Ubuntu build image needs a hefty 4+ GB of memory to work with. But if you set up a custom Debian build correctly, you can bring that down to just 1 GB. And when you’re running lots of services in the cloud, these numbers of extra memory usage start adding up in your monthly bill.&lt;/p&gt;

&lt;h1&gt;
  
  
  Setting Up Your Rust + Catboost Build Environment
&lt;/h1&gt;

&lt;p&gt;Let me walk you through setting up a Debian-based environment for Catboost. I’ll explain not just what to do, but why each step matters.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo5cme7velj1uv4c2hjsa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo5cme7velj1uv4c2hjsa.png" alt="Possible (and recommended) file structure" width="600" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing Catboost
&lt;/h2&gt;

&lt;p&gt;On the Rust side it happens as simple as with other crates in Rust:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[package]
name = "MLApp"
version = "0.1.0"
edition = "2021"

[dependencies]
catboost = { git = "https://github.com/catboost/catboost", rev = "0bfdc35"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However Catboost crate does not have precompiled C/C++ bindings and during installation (&lt;code&gt;cargo build&lt;/code&gt;) will try to compile it from sources specifically for your environment. So let’s set up our environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting with the Right Base Image
&lt;/h2&gt;

&lt;p&gt;First, we’re going with &lt;code&gt;debian:bookworm-slim&lt;/code&gt; as our base image. Why? It comes with CMake 3.24+, which we need for our build process. The ‘slim’ variant keeps our image size down, which is always nice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up the C++ Build Environment
&lt;/h2&gt;

&lt;p&gt;We need a bunch of C++ packages, and while I’m using version 16 in our setup, you actually have flexibility here. Any version that supports &lt;code&gt;-mno-outline-atomics&lt;/code&gt; will work fine.&lt;/p&gt;

&lt;p&gt;Let’s break down our package installation into logical groups.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting Up Package Sources
&lt;/h3&gt;

&lt;p&gt;First, we need to get our package sources in order. This part is crucial for getting the right LLVM tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RUN apt-get update &amp;amp;&amp;amp; DEBIAN_FRONTEND=noninteractive apt-get install -y - no-install-recommends \
 # will use it to download packages
 wget \ 
 # cryptographic package to verify LLVM sources 
 gnupg \
 # check Debian version to get correct LLVM package
 lsb-release \
 # package management helper
 software-properties-common
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we need to add LLVM’s repository.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RUN wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - \
 &amp;amp;&amp;amp; echo "deb http://apt.llvm.org/$(lsb_release -sc)/ llvm-toolchain-$(lsb_release -sc)-16 main" \
 &amp;gt;&amp;gt; /etc/apt/sources.list.d/llvm.list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The main step of installing packages
&lt;/h3&gt;

&lt;p&gt;We need quite a few packages, and I recommend to organize them by purpose — it makes maintenance so much easier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RUN apt-get update &amp;amp;&amp;amp; DEBIAN_FRONTEND=noninteractive apt-get install -y - no-install-recommends \
 # Basic build essentials
 build-essential \
 pkg-config \

 # Core development packages
 libssl-dev \
 cmake \
 ninja-build \
 python3-pip \

 # LLVM toolchain - version 16 works great, but any version with 
 # -mno-outline-atomics support will do
 clang-16 \
 libc++-16-dev \
 libc++abi-16-dev \
 lld-16 \

 # Don't forget git!
 git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next step cost me some time to figure out. Catboost expects to find clang in &lt;code&gt;/usr/bin/clang&lt;/code&gt;, but our installation puts it in &lt;code&gt;/usr/bin/clang-16&lt;/code&gt;. That’s why we have this bit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RUN ln -sf /usr/bin/clang-16 /usr/bin/clang &amp;amp;&amp;amp; \
 ln -sf /usr/bin/clang++-16 /usr/bin/clang++ &amp;amp;&amp;amp; \
 ln -sf /usr/bin/lld-16 /usr/bin/lld
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And do not forget to set up environment variables&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ENV CC=/usr/bin/clang
ENV CXX=/usr/bin/clang++
ENV LIBCLANG_PATH=/usr/lib/llvm-16/lib
ENV LLVM_CONFIG_PATH=/usr/bin/llvm-config-16
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Managing Dependencies
&lt;/h3&gt;

&lt;p&gt;We need Conan (version 2.4.1+) for handling C++ dependencies. A word of caution about the installation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RUN pip3 install --break-system-packages "conan==2.11.0"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;--break-system-packages&lt;/code&gt; flag might look scary, but it’s actually the easiest way I found to install Python packages system-wide in newer Debian versions. Besides we won’t be using much Python anyway in our build image.&lt;/p&gt;

&lt;h3&gt;
  
  
  Smart Build Strategy
&lt;/h3&gt;

&lt;p&gt;Here’s a trick that’ll save you tons of build time during active development stage. Split your build into two steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;First, build just the dependencies:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;COPY ./Cargo.* ./
RUN mkdir src &amp;amp;&amp;amp; \
 echo "fn main() {}" &amp;gt; src/main.rs &amp;amp;&amp;amp; \
 RUSTFLAGS="-C codegen-units=1" cargo build - release
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Important note here. You need that &lt;code&gt;RUSTFLAGS="-C codegen-units=1"&lt;/code&gt; flag because it ensures that C++ and Rust play along.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Then build your actual application:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;COPY ./src src
RUN cargo build - release
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way, Docker caches the dependency build, and you only rebuild your app code when it changes. Much faster!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fulo22ogs67g6h1sriuw2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fulo22ogs67g6h1sriuw2.png" alt="Build flow" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  A Critical Warning About Memory
&lt;/h3&gt;

&lt;p&gt;This is important: during the C++ build steps, you’ll need machine with 20+ GB of memory (I used 32Gb). And here’s the part that cost me almost a day of debugging — if you don’t have enough memory, you won’t get a clear error message (or any to be honest). Instead, your build will mysteriously timeout, leaving you wondering what went wrong. I learned this one the hard way!&lt;/p&gt;

&lt;h1&gt;
  
  
  Wrapping It Up
&lt;/h1&gt;

&lt;p&gt;Now we have a working Rust environment with Catboost that can handle all the good stuff: categories, text data, embeddings. Getting here wasn’t exactly easy.&lt;/p&gt;

&lt;p&gt;Next time we’ll cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Building an Axum web service&lt;/li&gt;
&lt;li&gt;Smart model loading patterns&lt;/li&gt;
&lt;li&gt;Real-world performance tricks I learned along the way&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So we’ll turn this foundation into something that can actually serve models in production! I ran into some interesting problems while figuring this out, like accidentally loading the same model multiple times for each handler call.&lt;/p&gt;

&lt;p&gt;And if you’ve tried this out and hit any weird issues, let me know. It’s always interesting to hear what problems other people run into.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gist.github.com/iKintosh/66c10f51597a68ba13a37cf1b7886da9" rel="noopener noreferrer"&gt;Full Dockerfile&lt;/a&gt;&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>rust</category>
      <category>ai</category>
      <category>machinelearning</category>
    </item>
  </channel>
</rss>
