<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:base="">
  <title></title>
  <subtitle></subtitle>
  <link href="/feed.xml" rel="self"/>
  <link href="/"/>
  <updated>2023-12-16T00:00:00Z</updated>
  <id>/</id>
  <author>
    <name></name>
    <email></email>
  </author>
  <entry>
    <title>IAM Role Authentication for Postgres RDS using Python and Go</title>
    <link href="/blog/iam-role-authentication-postgres-rds/"/>
    <updated>2021-12-28T00:00:00Z</updated>
    <id>/blog/iam-role-authentication-postgres-rds/</id>
    <content type="html"><![CDATA[<p>In this article I will be discussing how to connect to Postgres RDS from <a href="https://www.python.org/">Python</a> using <a href="https://www.djangoproject.com/">Django</a> and also using <a href="https://go.dev/">Go</a> when using IAM authentication.</p>
<p>AWS supports IAM Roles to authenticate with RDS instead of conventional username password. We can create IAM policies for RDS and attach it to an EC2 instance and the EC2 will be able to connect with RDS using an IAM token instead of the password. In this article we will talk about how to periodically refresh the token when using <code>pgx</code> as the <code>postgres</code> driver in <code>go</code>. Or how to use/refresh IAM tokens when using <code>django</code> and <code>python</code>.</p>
<p>In this blog I will not go into the details of creating and attaching an IAM role/policy, there are a lot of <a href="https://aws.amazon.com/premiumsupport/knowledge-center/users-connect-rds-iam/">resources</a> available for this.</p>
<h2 id="what-is-iam-role-and-iam-token" tabindex="-1"><a class="header-anchor" href="#what-is-iam-role-and-iam-token">What is IAM role and IAM token</a></h2>
<p>With IAM database authentication, you use an authentication token when you connect to your DB instance. An authentication token is a string of characters that you use instead of a password. After you generate an authentication token, it's valid for 15 minutes before it expires. If you try to connect using an expired token, the connection request is denied.</p>
<p>Network traffic to and from the database is encrypted using Transport Layer Security (TLS).</p>
<p>Using this authentication we can avoid keeping the password in the code/config. The AWS SDK will programmatically create and sign an authentication token.</p>
<h2 id="how-does-the-iam-token-work" tabindex="-1"><a class="header-anchor" href="#how-does-the-iam-token-work">How does the IAM token work</a></h2>
<p>The AWS SDK for <code>go</code> in case of <code>Golang</code> or <code>boto3</code> in case of <code>Python</code> is used to generate a new token every time a db connection needs to be made. Important thing to note is that the <strong>token is generated on the client side</strong>. No network call is made to <em>fetch</em> the token from AWS. So it is completely okay to generate a new token every time a new connection is made.</p>
<p>How this is achieved is using AWS's <a href="https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html">Signature v4 signing process</a>. AWS SDK signs the token with the access key ID and the secret access key.</p>
<p>We use this token to connect to Postgres instead of the password.</p>
<h2 id="how-can-this-be-used-to-connect-to-postgres-in-go-python" tabindex="-1"><a class="header-anchor" href="#how-can-this-be-used-to-connect-to-postgres-in-go-python">How can this be used to connect to Postgres in go/python</a></h2>
<p>AWS has quite good <a href="https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.Connecting.Go.html">documentation</a> for connecting with Postgres/RDS using IAM tokens instead of passwords in <code>go</code> as well as in <code>python</code>. But when I was trying to implement this, the problem I faced was that we used client side <a href="https://github.com/jackc/pgx">database pools</a>. It would be fair to assume that most real-world production systems leverage pooling.</p>
<h3 id="database-pooling" tabindex="-1"><a class="header-anchor" href="#database-pooling">Database Pooling</a></h3>
<p><a href="https://en.wikipedia.org/wiki/Connection_pool">Database connection pooling</a> is a method used to keep database connections open so they can be reused by others.</p>
<p>Typically, opening a database connection is an expensive operation. You have to open up network sessions, authenticate and so on. Pooling keeps the connections active so that, when a connection is later requested, one of the active ones is used in preference to having to create another one.</p>
<p>Now this adds an additional layer of complexity. The pools are created lazily. That means if a pool has 50 connections, all the connections are not stood up when the app starts, the connections are added to the pool as and when queries/transactions arrive. Now if we were to reuse the IAM token, we would have to keep track of them. So we would need a timer to refresh the token every 15 mins. But we cannot really close the connections within a pool as these are self-managing pools. So we would have to close the pools and re-initialize everything again. You can see where I am going with this. This approach seems very hacky and is not elegant.</p>
<h3 id="go-implementation" tabindex="-1"><a class="header-anchor" href="#go-implementation"><code>Go</code> implementation</a></h3>
<p>Fortunately <a href="https://github.com/jackc/pgx/issues/676"><code>pgx</code> has recently added</a> a <code>BeforeConnect</code> hook to its API. We can use this hook to modify the db credentials before any connection is made. This way every connection in the pool will have its own IAM token and as established earlier, tokens are created on the client side so there is no network call to &quot;fetch&quot; the token.</p>
<p>But how do we refresh the token after 15 mins? We don't! We can set the connection lifetime to 14 mins, so the connection is closed after 14 mins and the new connection that comes up, is created using a new token. This way we can inject our token for every connection. This approach also works because we are not closing the complete pool, we only close one connection at a time (because the connection lifetime is per connection). And because each connection is established lazily and has its own token, the connections are created and closed at different times, so the pool should always have some connections for use.</p>
<pre class="language-go"><code class="language-go"><span class="token comment">// peer contains the connection pool</span>
<span class="token comment">// IAMRoleAuth is the flag to toggle IAM role based authentication</span>
<span class="token keyword">type</span> peer <span class="token keyword">struct</span> <span class="token punctuation">{</span>
	name        <span class="token builtin">string</span>
	dbPool      <span class="token operator">*</span>pgxpool<span class="token punctuation">.</span>Pool
	weight      <span class="token builtin">int</span>
	logger      log<span class="token punctuation">.</span>Logger
	mu          sync<span class="token punctuation">.</span>Mutex
	IAMRoleAuth <span class="token builtin">bool</span>
<span class="token punctuation">}</span>

<span class="token keyword">type</span> DBConfig <span class="token keyword">struct</span> <span class="token punctuation">{</span>
	Host        <span class="token builtin">string</span>
	Port        <span class="token builtin">int</span>
	User        <span class="token builtin">string</span>
	Password    <span class="token builtin">string</span>
	SSLMode     <span class="token builtin">string</span>
	Name        <span class="token builtin">string</span>
	MinConn     <span class="token builtin">int</span>
	MaxConn     <span class="token builtin">int</span>
	LifeTime    <span class="token builtin">string</span>
	IdleTime    <span class="token builtin">string</span>
	LogLevel    <span class="token builtin">string</span>
	Region      <span class="token builtin">string</span>
	IAMRoleAuth <span class="token builtin">bool</span>
<span class="token punctuation">}</span></code></pre>
<p>We create a <code>session</code> struct, and pass this on to create a new IAM token.</p>
<pre class="language-go"><code class="language-go"><span class="token keyword">import</span> <span class="token string">"github.com/aws/aws-sdk-go/aws/session"</span>

opts <span class="token operator">:=</span> session<span class="token punctuation">.</span>Options<span class="token punctuation">{</span>Config<span class="token punctuation">:</span> aws<span class="token punctuation">.</span>Config<span class="token punctuation">{</span>
		CredentialsChainVerboseErrors<span class="token punctuation">:</span> aws<span class="token punctuation">.</span><span class="token function">Bool</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
		Region<span class="token punctuation">:</span>                        aws<span class="token punctuation">.</span><span class="token function">String</span><span class="token punctuation">(</span><span class="token string">"us-east-1"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
		MaxRetries<span class="token punctuation">:</span>                    aws<span class="token punctuation">.</span><span class="token function">Int</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
	<span class="token punctuation">}</span><span class="token punctuation">}</span>
sess <span class="token operator">:=</span> session<span class="token punctuation">.</span><span class="token function">Must</span><span class="token punctuation">(</span>session<span class="token punctuation">.</span><span class="token function">NewSessionWithOptions</span><span class="token punctuation">(</span>opts<span class="token punctuation">)</span><span class="token punctuation">)</span></code></pre>
<pre class="language-go"><code class="language-go"><span class="token comment">// getDBPool returns a new pgxpool.Pool instance.</span>
<span class="token comment">// If peer IAMRoleAuth is true then BeforeConnect method is implemented</span>
<span class="token comment">// BeforeConnect() is used to inject the authToken before a connection is made.</span>
<span class="token comment">// Connection `LifeTime` is set to 14 mins, hence the connection will expire </span>
<span class="token comment">// automatically and no intervention is needed to close the connection.</span>
<span class="token keyword">func</span> <span class="token punctuation">(</span>p <span class="token operator">*</span>peer<span class="token punctuation">)</span> <span class="token function">getDBPool</span><span class="token punctuation">(</span>ctx context<span class="token punctuation">.</span>Context<span class="token punctuation">,</span> cfg DBConfig<span class="token punctuation">,</span> sess <span class="token operator">*</span>session<span class="token punctuation">.</span>Session<span class="token punctuation">)</span> <span class="token punctuation">(</span><span class="token operator">*</span>pgxpool<span class="token punctuation">.</span>Pool<span class="token punctuation">,</span> <span class="token builtin">error</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>

	poolCfg<span class="token punctuation">,</span> err <span class="token operator">:=</span> pgxpool<span class="token punctuation">.</span><span class="token function">ParseConfig</span><span class="token punctuation">(</span><span class="token function">getDBURL</span><span class="token punctuation">(</span>cfg<span class="token punctuation">)</span><span class="token punctuation">)</span>
	<span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
		p<span class="token punctuation">.</span>logger<span class="token punctuation">.</span><span class="token function">Err</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Msgf</span><span class="token punctuation">(</span><span class="token string">"unable to parse config for peer: %v cfg: %v"</span><span class="token punctuation">,</span> cfg<span class="token punctuation">.</span>Name<span class="token punctuation">,</span> cfg<span class="token punctuation">)</span>
		<span class="token keyword">return</span> <span class="token boolean">nil</span><span class="token punctuation">,</span> err
	<span class="token punctuation">}</span>

	<span class="token keyword">if</span> p<span class="token punctuation">.</span>IAMRoleAuth <span class="token punctuation">{</span>
		poolCfg<span class="token punctuation">.</span>BeforeConnect <span class="token operator">=</span> <span class="token keyword">func</span><span class="token punctuation">(</span>ctx context<span class="token punctuation">.</span>Context<span class="token punctuation">,</span> config <span class="token operator">*</span>pgx<span class="token punctuation">.</span>ConnConfig<span class="token punctuation">)</span> <span class="token builtin">error</span> <span class="token punctuation">{</span>
			p<span class="token punctuation">.</span>logger<span class="token punctuation">.</span><span class="token function">Info</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Msg</span><span class="token punctuation">(</span><span class="token string">"RDS Credential beforeConnect(), creating new credential"</span><span class="token punctuation">)</span>
			newPassword<span class="token punctuation">,</span> err <span class="token operator">:=</span> p<span class="token punctuation">.</span><span class="token function">getCredential</span><span class="token punctuation">(</span>poolCfg<span class="token punctuation">,</span> cfg<span class="token punctuation">,</span> sess<span class="token punctuation">)</span>
			<span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
				<span class="token keyword">return</span> err
			<span class="token punctuation">}</span>
			p<span class="token punctuation">.</span>mu<span class="token punctuation">.</span><span class="token function">Lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
			config<span class="token punctuation">.</span>Password <span class="token operator">=</span> newPassword
			p<span class="token punctuation">.</span>mu<span class="token punctuation">.</span><span class="token function">Unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
			<span class="token keyword">return</span> <span class="token boolean">nil</span>
		<span class="token punctuation">}</span>
	<span class="token punctuation">}</span>

	pool<span class="token punctuation">,</span> err <span class="token operator">:=</span> pgxpool<span class="token punctuation">.</span><span class="token function">ConnectConfig</span><span class="token punctuation">(</span>ctx<span class="token punctuation">,</span> poolCfg<span class="token punctuation">)</span>
	<span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
		p<span class="token punctuation">.</span>logger<span class="token punctuation">.</span><span class="token function">Err</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Msg</span><span class="token punctuation">(</span><span class="token string">"unable to connect to db"</span><span class="token punctuation">)</span>
		<span class="token keyword">return</span> <span class="token boolean">nil</span><span class="token punctuation">,</span> err
	<span class="token punctuation">}</span>

	<span class="token keyword">return</span> pool<span class="token punctuation">,</span> <span class="token boolean">nil</span>
<span class="token punctuation">}</span>

<span class="token comment">// getCredential returns the new password to connect to RDS</span>
<span class="token keyword">func</span> <span class="token punctuation">(</span>p <span class="token operator">*</span>peer<span class="token punctuation">)</span> <span class="token function">getCredential</span><span class="token punctuation">(</span>poolCfg <span class="token operator">*</span>pgxpool<span class="token punctuation">.</span>Config<span class="token punctuation">,</span> cfg DBConfig<span class="token punctuation">,</span> sess <span class="token operator">*</span>session<span class="token punctuation">.</span>Session<span class="token punctuation">)</span> <span class="token punctuation">(</span><span class="token builtin">string</span><span class="token punctuation">,</span> <span class="token builtin">error</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	dbEndpoint <span class="token operator">:=</span> fmt<span class="token punctuation">.</span><span class="token function">Sprintf</span><span class="token punctuation">(</span><span class="token string">"%s:%d"</span><span class="token punctuation">,</span> poolCfg<span class="token punctuation">.</span>ConnConfig<span class="token punctuation">.</span>Host<span class="token punctuation">,</span> poolCfg<span class="token punctuation">.</span>ConnConfig<span class="token punctuation">.</span>Port<span class="token punctuation">)</span>
	awsRegion <span class="token operator">:=</span> cfg<span class="token punctuation">.</span>Region
	dbUser <span class="token operator">:=</span> poolCfg<span class="token punctuation">.</span>ConnConfig<span class="token punctuation">.</span>User
	authToken<span class="token punctuation">,</span> err <span class="token operator">:=</span> rdsutils<span class="token punctuation">.</span><span class="token function">BuildAuthToken</span><span class="token punctuation">(</span>dbEndpoint<span class="token punctuation">,</span> awsRegion<span class="token punctuation">,</span> dbUser<span class="token punctuation">,</span> sess<span class="token punctuation">.</span>Config<span class="token punctuation">.</span>Credentials<span class="token punctuation">)</span>
	<span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
		p<span class="token punctuation">.</span>logger<span class="token punctuation">.</span><span class="token function">Panic</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Err</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Msg</span><span class="token punctuation">(</span><span class="token string">"Error in building auth token to connect with RDS"</span><span class="token punctuation">)</span>
		<span class="token keyword">return</span> <span class="token string">""</span><span class="token punctuation">,</span> err
	<span class="token punctuation">}</span>
	<span class="token keyword">return</span> authToken<span class="token punctuation">,</span> <span class="token boolean">nil</span>
<span class="token punctuation">}</span></code></pre>
<p>If you are using <a href="https://github.com/jmoiron/sqlx"><code>sqlx</code></a> as the DB driver, then things get trickier. As <code>sqlx</code> does not provide the <code>BeforeConnect()</code> hook to inject the token. I have not found any elegant way to do this with <code>sqlx</code> yet.</p>
<h3 id="django-implementation" tabindex="-1"><a class="header-anchor" href="#django-implementation"><code>Django</code> implementation</a></h3>
<p><a href="https://www.djangoproject.com/">Django</a> is a very popular web framework in python.</p>
<p>Unlike the <code>go</code> implementation, <code>django</code> does not use database pools, instead <code>django</code> uses <a href="https://docs.djangoproject.com/en/4.0/ref/databases/#persistent-connections">persistent connections</a>. This is different from pooling because <code>django</code> maintains one persistent connection per thread. In pooling, the pool takes care of opening and closing connections and every thread that needs a connection requests one from the pool.</p>
<p><code>django</code> uses <code>postgres</code> database backend which internally uses <a href="https://www.psycopg.org/"><code>Psycopg</code></a> as the database adapter. We can create our custom database backend by extending the <code>postgres</code> backend.</p>
<pre class="language-python"><code class="language-python"><span class="token keyword">def</span> <span class="token function">get_aws_connection_params</span><span class="token punctuation">(</span>params<span class="token punctuation">)</span><span class="token punctuation">:</span>
    <span class="token triple-quoted-string string">"""
    "rds_iam_auth" : If set to `True` IAM authentication is used, 
                     else password based authentication is used
    "region_name" : Contains the name of the aws region where DB is present.

    Parameters
    ----------
        params : dict
            The DATABASES dict that is passed from the settings.py
    """</span>
    enabled <span class="token operator">=</span> params<span class="token punctuation">.</span>pop<span class="token punctuation">(</span><span class="token string">"rds_iam_auth"</span><span class="token punctuation">,</span> <span class="token boolean">False</span><span class="token punctuation">)</span>
    <span class="token keyword">if</span> enabled<span class="token punctuation">:</span>
        region_name <span class="token operator">=</span> params<span class="token punctuation">.</span>pop<span class="token punctuation">(</span><span class="token string">"region_name"</span><span class="token punctuation">,</span> <span class="token boolean">None</span><span class="token punctuation">)</span>
        rds_client <span class="token operator">=</span> boto3<span class="token punctuation">.</span>client<span class="token punctuation">(</span>service_name<span class="token operator">=</span><span class="token string">"rds"</span><span class="token punctuation">,</span> region_name<span class="token operator">=</span>region_name<span class="token punctuation">)</span>

        hostname <span class="token operator">=</span> params<span class="token punctuation">.</span>get<span class="token punctuation">(</span><span class="token string">"host"</span><span class="token punctuation">)</span>
        hostname <span class="token operator">=</span> hostname <span class="token keyword">if</span> hostname <span class="token keyword">else</span> <span class="token string">"localhost"</span>

        params<span class="token punctuation">[</span><span class="token string">"password"</span><span class="token punctuation">]</span> <span class="token operator">=</span> rds_client<span class="token punctuation">.</span>generate_db_auth_token<span class="token punctuation">(</span>
            DBHostname<span class="token operator">=</span>hostname<span class="token punctuation">,</span>
            Port<span class="token operator">=</span>params<span class="token punctuation">.</span>get<span class="token punctuation">(</span><span class="token string">"port"</span><span class="token punctuation">,</span> <span class="token number">5432</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
            DBUsername<span class="token operator">=</span>params<span class="token punctuation">.</span>get<span class="token punctuation">(</span><span class="token string">"user"</span><span class="token punctuation">,</span> getpass<span class="token punctuation">.</span>getuser<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token punctuation">)</span>

    <span class="token keyword">return</span> params

<span class="token keyword">class</span> <span class="token class-name">DatabaseWrapper</span><span class="token punctuation">(</span>base<span class="token punctuation">.</span>DatabaseWrapper<span class="token punctuation">)</span><span class="token punctuation">:</span>
    <span class="token keyword">def</span> <span class="token function">get_connection_params</span><span class="token punctuation">(</span>self<span class="token punctuation">)</span><span class="token punctuation">:</span>
        params <span class="token operator">=</span> <span class="token builtin">super</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>get_connection_params<span class="token punctuation">(</span><span class="token punctuation">)</span>
        params<span class="token punctuation">.</span>setdefault<span class="token punctuation">(</span><span class="token string">"port"</span><span class="token punctuation">,</span> <span class="token number">5432</span><span class="token punctuation">)</span>
        <span class="token keyword">return</span> get_aws_connection_params<span class="token punctuation">(</span>params<span class="token punctuation">)</span></code></pre>
<p>In the <code>settings.py</code> we can use this backend as:</p>
<pre class="language-python"><code class="language-python">DATABASES <span class="token operator">=</span> <span class="token punctuation">{</span>
    <span class="token string">"default"</span><span class="token punctuation">:</span> <span class="token punctuation">{</span>
        <span class="token string">"ENGINE"</span><span class="token punctuation">:</span> <span class="token string">"path.to.backend.aws.postgres"</span><span class="token punctuation">,</span>
        <span class="token string">"NAME"</span><span class="token punctuation">:</span> <span class="token string">"postgres"</span><span class="token punctuation">,</span>
        <span class="token string">"USER"</span><span class="token punctuation">:</span> <span class="token string">"user"</span><span class="token punctuation">,</span>
        <span class="token string">"PASSWORD"</span><span class="token punctuation">:</span> <span class="token string">"welcome"</span><span class="token punctuation">,</span>
        <span class="token string">"HOST"</span><span class="token punctuation">:</span> <span class="token string">"localhost"</span><span class="token punctuation">,</span>
        <span class="token string">"PORT"</span><span class="token punctuation">:</span> <span class="token string">"5432"</span><span class="token punctuation">,</span>
        <span class="token string">"OPTIONS"</span><span class="token punctuation">:</span> <span class="token punctuation">{</span>
            <span class="token string">"rds_iam_auth"</span><span class="token punctuation">:</span> <span class="token boolean">True</span><span class="token punctuation">,</span>
            <span class="token string">"region_name"</span><span class="token punctuation">:</span> <span class="token string">"us-east-1"</span><span class="token punctuation">,</span>
            <span class="token string">"sslmode"</span><span class="token punctuation">:</span> <span class="token string">"verify-full"</span><span class="token punctuation">,</span>
            <span class="token string">"sslrootcert"</span><span class="token punctuation">:</span> <span class="token string">"/path/to/rds-ca-2019-root.pem"</span><span class="token punctuation">,</span>
        <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span></code></pre>
<p><strong>Another thing to note in both the above approaches, is that the hostname needs to be used to connect with the RDS PostgreSQL, if a <code>DNS</code> <code>CNAME</code> is used, the connection will fail with PEM authentication error.</strong> This happens because the IAM token is signed for the <code>CNAME</code>, but RDS expects the <code>hostname</code>. One work-around is to resolve the CNAME into the hostname in the custom backend, before making the connection. I would advise against this, because CNAMEs are cached at multiple places and have a certain TTL, the connection would fail if the CNAME has not propagated yet.</p>
<h2 id="references" tabindex="-1"><a class="header-anchor" href="#references">References</a></h2>
<ul>
<li><a href="https://stackoverflow.com/questions/4041114/what-is-database-pooling">What is database pooling</a></li>
<li><a href="https://github.com/labd/django-iam-dbauth">django-iam-dbauth</a></li>
<li>Included inline hyperlinks to most sources.</li>
</ul>
]]></content>
    <category term="technical"/>
  </entry>
  <entry>
    <title>My Experience Buying a Term Insurance</title>
    <link href="/blog/buying-term-insurance/"/>
    <updated>2022-01-20T00:00:00Z</updated>
    <id>/blog/buying-term-insurance/</id>
    <content type="html"><![CDATA[<p>So recently I set out to buy a new term insurance plan for myself. A term insurance is necessary to protect ones dependents in case of death. You do not want to transfer your financial burdens on your dependents.</p>
<p>A term insurance is a very simple insurance where a person is insured for a &quot;term&quot; (say 30 years). You are required to pay the premium for this duration. During the term if you pass away, then your nominee receives the covered sum. However if at the end of the term if you are still alive, you don't get back anything. The premiums of these insurance policies are lower if you purchase them at a relatively young age. The premiums are fixed throughout the term of the policy, so it makes sense to buy these when young.</p>
<p>There are some people with another school of thought who say that you should get a term insurance only after you have dependents. This is also okay, but I personally would recommend getting one as soon as possible.</p>
<p>Now why term insurance is the right insurance for most people and how keeping your investments and insurances separate is the right thing to do is a discussion for a separate article. If you proceed to read further I assume you know why a term insurance is better than a ULIP.</p>
<p>In this article I will talk about how I purchased my term insurance and what are the things one needs to look out for.</p>
<h2 id="step-1-plan" tabindex="-1"><a class="header-anchor" href="#step-1-plan">Step 1 - Plan</a></h2>
<p>It is usually recommended that the policy cover should be 10-20x your annual salary. I personally agree with this, the rationale behind this is that in the event of your death your family must be able to maintain their current lifestyle for the next 10-20 years. We also need to understand what &quot;term&quot; we want our insurance for. The idea is simple, we want the insurance till the time we are earning and we have dependents. So for most people I believe that would be the age of 58-60. By then most people would retire and their children would be self sufficient. There is no point of having an insurance if you aren't working and don't have any dependents.</p>
<p>Insurance plans also come with a load of riders like, Critical Illness Rider and Accidental Death Rider and a few others. In my opinion one should opt for a vanilla insurance as most of these riders are useless. Because we should have a separate health insurance for these things anyway.</p>
<h2 id="step-2-research" tabindex="-1"><a class="header-anchor" href="#step-2-research">Step 2 - Research</a></h2>
<p>For this, I just used <a href="http://policybazaar.com">policybazaar.com</a>. It gives an easy way to look at the estimated premiums and claim settlement ratios etc on a single page. You can filter and sort based on the parameters important to you. While buying my policy I observed that most of the companies have claim settlement ratios etc greater than 97-98%. But one thing to keep in mind is that these claim settlement ratios are for all their insurance policies, which include ULIPs etc which are usually for low amounts. An insurance company can very easily approve 99 ULIPs with a cover of 5 lakh and reject 1 term insurance with a cover of 20 crore. As of now there really is no published data to look into this.</p>
<p>But the bottom line is that you can go with any major insurance company as you don't want your nominee to go through trouble if they ever need to file for a claim. In my case I chose ICICI Prudential. I chose my term of 38 years and looked at the premium and other policy details. Policy bazaar said that there would be a &quot;Tele medical&quot; before this policy was issued.</p>
<p>I had seen many reviews of policy bazaar saying that it was taking longer time to issue a policy through policy bazaar than buying directly from the company. So I decided to purchase directly from ICICI Pru.</p>
<h2 id="step-3-buying-the-policy" tabindex="-1"><a class="header-anchor" href="#step-3-buying-the-policy">Step 3 - Buying the policy</a></h2>
<p>I created an account on the insurer's website, filled the required details. Needless to say, don't lie on your forms as your claim might get rejected. It is fine to pay some extra premium but don't hide any medical history. For the insurance I would recommend just paying the premiums annually, against opting for any kind of limited pay. I paid the premium online. Yes, I paid the premium before the medical was done. It is possible that if there are some issues in the medical report, you might have to pay the extra premium. At this stage I was told that tele medical is not available so there will be a physical medical and sample will be collected from home.</p>
<h2 id="step-4-medical-tests" tabindex="-1"><a class="header-anchor" href="#step-4-medical-tests">Step 4 - Medical Tests</a></h2>
<p>I received a message from the medical partners of ICICI where I was asked to choose a time slot for the medical test. Here when I input my address I was prompted that home collection is not available in this area. This was very weird because I live in a very well known locality in North Bangalore. Since I had already paid the premium, I decided to go ahead with the medical. The lab was just 5 minutes from my house.</p>
<p>I went in at the selected time, everything was very smooth. I gave all my samples.</p>
<p>Within 48 hours, ICICI sent me the reports which is a great thing, because I've heard that many insurers don't send the medical reports back to the customers.</p>
<h2 id="step-5-issuance-of-the-policy" tabindex="-1"><a class="header-anchor" href="#step-5-issuance-of-the-policy">Step 5 - Issuance of the policy</a></h2>
<p>Within 2 days after receiving my medical reports, I was issued the policy. I received the policy documents over email on the same day. And within 14 days I received the physical policy documents. I was able to login to their website using my email and could verify that all the details were correct.</p>
<h2 id="summary" tabindex="-1"><a class="header-anchor" href="#summary">Summary</a></h2>
<p>All in all the process was not very difficult, having said that, it wasn't the easiest. Tele medical or home sample collection would've been great. If you don't have a term insurance yet, I'd suggest you get one sooner rather than later.</p>
<p>Everything mentioned in this article is my opinion and is from personal experience, your mileage may vary.</p>
<p>If you'd like to know more, do check out:</p>
<ol>
<li><a href="https://www.indiainvestments.wiki/start-here/insurance-policies/life/term-insurance">What is Life Insurance</a></li>
<li><a href="https://www.youtube.com/watch?v=aTQUB_1jREg">How to choose a term insurance</a></li>
</ol>
]]></content>
    <category term="opinions"/>
  </entry>
  <entry>
    <title>How to Excel in your First Job as a Software Engineer</title>
    <link href="/blog/how-to-excel-first-job-software-engineer/"/>
    <updated>2022-02-15T00:00:00Z</updated>
    <id>/blog/how-to-excel-first-job-software-engineer/</id>
    <content type="html"><![CDATA[<p>Doing well in Software Development is not just about writing the most code in the least amount of time. Working in an engineering team is quite different from what most students are used to in their university maybe working alone, or working in small teams of 2-3. This post is aimed mostly at students who are in their final/pre-final year of university and will soon be entering their first job as a Software Engineer or an intern.</p>
<p>When I started working as an intern, I had the luxury of meeting people physically, going on team lunches etc. I could just walk over to someone if I had a question. These things really helped me build very good working relationship with my teammates, manager etc. However, with most offices still closed and most job opportunities becoming remote, it becomes all the more important to reach out and seek for help/feedback and make personal connections with your colleagues.</p>
<p>This article is based on my experience over the past 3 years, most things on here are just my opinion and its totally okay if you don't agree.</p>
<p>When transitioning away from academics one needs to understand the paradigm shift. Some things which were important in university are not very important anymore, while some other things like readability of code are the most important thing. Because of this, a lot of students struggle transitioning from academia to industry. In my experience the best students don't always make the best engineers. Students are typically used to working alone and are not used to taking feedback. In most universities the projects are delivered at once and students are not used to working on massive projects and making incremental progress.</p>
<h2 id="8-ways-to-become-a-better-engineer" tabindex="-1"><a class="header-anchor" href="#8-ways-to-become-a-better-engineer">8 ways to become a better engineer</a></h2>
<h3 id="take-initiative" tabindex="-1"><a class="header-anchor" href="#take-initiative">Take initiative</a></h3>
<p>Don't be afraid of taking initiative. Volunteer for new projects, volunteer if anyone needs some help. This will open up new opportunities for learning. It is okay if you don't know how to do something, you can always learn.</p>
<h3 id="participate-in-technical-discussions" tabindex="-1"><a class="header-anchor" href="#participate-in-technical-discussions">Participate in technical discussions</a></h3>
<p>I have seen most interns/junior engineers don't participate in technical discussions. I believe the reason for this is that they believe that they are not good enough and don't want to sound stupid. But you need to not worry about sounding stupid and if you have a question or a suggestion, you should almost always speak up.</p>
<p>NO ONE will judge you for not knowing something. However please don't ask something just for the sake of asking, that is just annoying.</p>
<h3 id="take-ownership" tabindex="-1"><a class="header-anchor" href="#take-ownership">Take ownership</a></h3>
<p>Taking ownership means that you will complete your assigned task from start to finish. You will ensure that you communicate with your team and the respective stakeholders in case there is any blocker or to keep everyone up to date on the status of the work, so that you reduce the risk of not getting your task done in time. This is something that junior engineers actively need to work on.</p>
<p>I have seen so many juniors who when given some task, just vanish and go into a cocoon for several weeks and come back after a few weeks having completed the work, or to tell that they are stuck with a problem. Taking ownership means that you need to show incremental progress and seek for feedback periodically. This will also help you develop trust with other team members as people will know what you are actively working on.</p>
<p>I cannot remember where I had read &quot;If your manager cannot trust you, it doesn't matter how smart you are&quot;. This makes complete sense, you need to build trust with your co-workers. You need to ensure that people don't have to follow up with you again and again.</p>
<h3 id="communicate-clearly" tabindex="-1"><a class="header-anchor" href="#communicate-clearly">Communicate clearly</a></h3>
<p>If you need something say it. If you need technical guidance, speak up. If there is something in your personal life that is taking up your time, it might make sense to share relevant information with your manager so that expectations can be set accordingly. If you have concerns regarding your promotion or your compensation, you should be able to talk to your manager about these things. Clear communication is very important.</p>
<h3 id="growth-mindset" tabindex="-1"><a class="header-anchor" href="#growth-mindset">Growth mindset</a></h3>
<p>A lot of junior engineers that I talk to, say things like &quot;I'm not good at front-end&quot; or &quot;I don't know about XYZ&quot;. Well, that might be true for the moment, but you can always learn whatever you don't know. Most people don't know most things. It is more important to be open to learning. Learning new things itself is a skill. This brings me to my next point.</p>
<h3 id="learn-to-learn" tabindex="-1"><a class="header-anchor" href="#learn-to-learn">Learn to learn</a></h3>
<p>The best engineers I know are open to learning. They are often faster at learning than &quot;others&quot;. This is not because they are smarter, but this is because they have developed this skill of learning new things over the years. Everyone has a different way of learning, figure out what works for you.</p>
<p>Usually you'll have to go outside your comfort zone for this. But the more new things you learn, the more you exercise your &quot;learning muscle&quot;. Be open to suggestions and new ideas. These can be in the form of code review or during stand up meetings. Just general discussions between other team members is a great learning opportunity. Listen carefully to what others are saying, if senior engineers are doing something a particular way there is almost always a reason they do it that way.</p>
<h3 id="ask" tabindex="-1"><a class="header-anchor" href="#ask">Ask</a></h3>
<p>If you don't know something, ask. No one will look down on you for not knowing something. And if they do, it is a reflection on them and not on you. It is better to ask a senior engineer who can explain something to you in 5 mins rather than spending 3 days trying to figure it out. However, please make a genuine effort to understand something before you go out and ask. You need to respect people's time. I see far too many junior developers who seek help at the first sight of inconvenience. This ties together with my previous point, if you don't struggle, you will not learn anything new.</p>
<h3 id="separate-work-from-personal-life" tabindex="-1"><a class="header-anchor" href="#separate-work-from-personal-life">Separate work from personal life</a></h3>
<p>Learn to switch off. This is something even I struggle with. You don't need to be online 24/7. If you receive an email at 11 PM you don't need to reply instantly. You don't need to work weekends. A lot of new engineers want to &quot;prove themselves&quot;, which is fine. But you need to create a sustainable work-life balance for yourself, it is not fun to feel burnt out in a couple of years.</p>
<p>Having said that the other extreme is bad too. I've seen people not joining important meetings which are beyond 5PM because they are &quot;off work&quot;, try to find the right balance that works for you.</p>
<h3 id="create-meaningful-relationships" tabindex="-1"><a class="header-anchor" href="#create-meaningful-relationships">Create meaningful relationships</a></h3>
<p>Human beings are social creatures. Good relationship with our co-workers will make our jobs enjoyable. Be empathetic towards your teammates, try to help wherever you can. Having a strong professional circle will also help you develop your career, opening up opportunities that otherwise might pass by you.</p>
<h2 id="conclusion" tabindex="-1"><a class="header-anchor" href="#conclusion">Conclusion</a></h2>
<p>Most of these things need to be done within reason, for example too much &quot;asking&quot; will come across as inability to learn or by taking up too many tasks you can stretch yourself too thin.</p>
<p>These were some of the things that I did as a junior engineer which helped me grow as an engineer. I still try to actively do these things. If you feel that I missed out on something or if there is something that you would do differently, please let me know.</p>
]]></content>
    <category term="technical"/>
  </entry>
  <entry>
    <title>IIT Bombay Half Marathon 2023</title>
    <link href="/blog/iit-bombay-half-marathon-2023/"/>
    <updated>2023-10-22T00:00:00Z</updated>
    <id>/blog/iit-bombay-half-marathon-2023/</id>
    <content type="html"><![CDATA[<div class="race-info">
<ul>
<li><strong>Event:</strong> IIT Bombay Half Marathon</li>
<li><strong>Date:</strong> October 22, 2023</li>
<li><strong>Distance:</strong> 21.1 km (Half Marathon)</li>
<li><strong>Location:</strong> IIT Bombay Campus, Mumbai</li>
<li><strong>Finish Time:</strong> 2:14:00</li>
<li><strong>Pace:</strong> ~6:20 min/km</li>
</ul>
</div>
<p>This race was a lot of firsts for me. First time running with a pacer, first time finding out about my race pace and the first time exceeding expectations.</p>
<p>I had virtually no racing experience coming into this. The only race I had ever done before this was the Bengaluru Marathon 2023. I had almost no training for this race because I completely stopped running after my marathon. I was introduced to this event by my friend <a href="https://www.strava.com/athletes/104686521">Abhinav</a>, he told me that he would be running this and its a good event. Coincidentally, I was in Mumbai at the same time, so I decided to register for it. It was supposed to be a fun and easy run. And because I had already done a full marathon I was certain I would be able to finish it.</p>
<p>Prior to this event, I had no idea of what &quot;race pace&quot; was meant to feel like. For me, race pace was just easy pace because anything quicker and I wouldn't even be able to finish the race.</p>
<h2 id="pre-race" tabindex="-1"><a class="header-anchor" href="#pre-race">Pre-Race</a></h2>
<p>The race was in the IIT Bombay campus. The walk from the main gate to the start line was quite long. It was a good day, not very warm as per Mumbai standards. I was not feeling great, I had mild diarrhea the morning of the race and consequently some dehydration.</p>
<p>I took a gel at the start line and kept another one for half way into the race. I had no idea what my target race pace should be. My 5k time at this point was 29 minutes, so keeping a 6:30 min/km pace for 21.1kms seemed difficult. I had completed the first half in my full marathon in around 2h40m. So I would be happy with 2:30 here.</p>
<h2 id="the-race" tabindex="-1"><a class="header-anchor" href="#the-race">The Race</a></h2>
<p>The race started off at 6 AM. In typical rookie fashion, I started off too quickly, I crossed the 3km mark at 16min30s, which was far too quick for me. It was faster than my 5k pace. I was ahead of the 2 hour pacer. I realized I was redlining and slowed down a bit. I was passed by the 2 hour pacer. I did the net couple kms at 6:30 min/km pace. It was at this point that I was passed by the 2h15min pacer, <a href="https://www.strava.com/activities/10122329261">Mr. Rakesh Garg</a>. I decided to hold onto this pacer for as long as I could. I quickly realized that running along with a pacer was very different and seemed much easier. We were consistently maintaining 6:15min/km pace and it didn't feel that hard. I just had to match the pacer's footsteps and not constantly look at my watch or worry about going too fast (or too slow) on a uphill section. Rakesh was a very motivating pacer, he would constantly encourage me to keep up when would see that my pace was slipping up. Around the 14th km, he told me, &quot;You can go faster, why don't you go ahead, and if it doesn't work out I am behind you anyway&quot;. This was all the push I needed. Around the 17th km, I broke away from the pace group and tried to pick up some pace. However i wasn't particularly quick, by this point in the race my legs had begun flailing. The lack of training and poor conditioning was very apparent. I kept pushing, holding around the same 6:15-6:20 pace.</p>
<p>I finished the race in 2h14mins. I did not think I had it in me, to maintain roughly 6:20min/km pace for the complete race. I had massively exceeded my expectations, a large part of the credit needs to go Rakesh, who had paced an amazing race and was very encouraging whenever I felt like I couldn't go on.</p>
<h2 id="the-course" tabindex="-1"><a class="header-anchor" href="#the-course">The Course</a></h2>
<p>The complete route was inside the IIT Bombay campus, it was a good route to race on with two laps of 10.5km each. The terrain was moderately hilly, with around 120m of elevation over 21kms.</p>
<h2 id="event-review" tabindex="-1"><a class="header-anchor" href="#event-review">Event Review</a></h2>
<p>In terms of logistics, the race was fairly well organized, the only qualm I had was the large water bottles which were given out at the aid stations. Obviously one wouldn't drink a litre of water while running, and we just had to lug around a large bottle of water for a large part of the race. Also, some pain relief spray at the aid stations would've been nice, as I ran the complete race with quite a lot of knee pain. A large number of runners were college students, who weren't very serious about the race, however I think the organizers did well to acomodate both serious and non-serious runners. The bib collection venue for this race was at a decathlon store in R City Mall. I would definitely love to run this race again, if I happen to be in Mumbai.</p>
<h2 id="post-race" tabindex="-1"><a class="header-anchor" href="#post-race">Post-Race</a></h2>
<p>Post race, we had breakfast and took some pictures with Abhinav, whom I had met for the first time since our Kang Yatse II expedition. I particularly remember the cab ride back to the hostel, the driver was very intrigued about road running and marathons. He was very happy to see so many young people taking up running and a healthy lifestyle, especially at a young age. Hard to disagree!</p>
<h2 id="strava-activity" tabindex="-1"><a class="header-anchor" href="#strava-activity">Strava Activity</a></h2>
<p><a href="https://www.strava.com/activities/10122204881/overview"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1707506142921/d9e7df8e-8524-476b-a477-649bb5d3c271.png" alt="Strava activity"></a></p>
<h2 id="tips-for-future-runners" tabindex="-1"><a class="header-anchor" href="#tips-for-future-runners">Tips for Future Runners</a></h2>
<p>Be prepared for a typical Mumbai, humid course.</p>
<hr>
<p><em>Have questions about this race? Feel free to reach out on <a href="https://twitter.com/TheMayankThakur">Twitter</a>.</em></p>
]]></content>
    <category term="race-reports"/>
  </entry>
  <entry>
    <title>HRX Pinkathon 5K</title>
    <link href="/blog/hrx-pinkathon-5k/"/>
    <updated>2023-11-26T00:00:00Z</updated>
    <id>/blog/hrx-pinkathon-5k/</id>
    <content type="html"><![CDATA[<div class="race-info">
<ul>
<li><strong>Event:</strong> HRX Pinkathon 5K</li>
<li><strong>Date:</strong> November 26, 2023</li>
<li><strong>Distance:</strong> 5 km</li>
<li><strong>Location:</strong> Cubbon Park, Bangalore</li>
<li><strong>Finish Time:</strong> Not Timed</li>
<li><strong>Pace:</strong> N/A</li>
</ul>
</div>
<p>The HRX Pinkathon was a 5k race that I ran on 26th November 2023. The race started at 6 AM, which I thought was quite early in the morning for a 5k.</p>
<h2 id="event-issues" tabindex="-1"><a class="header-anchor" href="#event-issues">Event Issues</a></h2>
<p>Without mincing my words, it was a very poorly organized race. The start was very crowded, the gates of Cubbon Park, weren't open, so all the thousand people had to squeeze through the tiny gates on the side. The race was NOT timed as well (technically it isn't even a race), the bibs didn't have a timing chip (and there were no timing mats, obviously) although, during the registration it was advertised as a timed race.</p>
<p>There were very few aid stations, from what I remember. The route wasn't marked clearly and there were no volunteers to guide the runners. I was running the 5k, there was also a 10k event which was two loops of 5k. I accidentally went into the 10k loop, as I apparently missed a turn. I kept going, till my watch showed 6.5km when I found a volunteer and asked him where the route was, he told me to keep going which was weird since my watch was already showing over 6km. Soon, there were half a dozen people with me asking the same volunteer where the 5k route ended. The volunteer didn't seem to know that there were two different events. We just ended up following Google Maps back to the start line.</p>
<h2 id="event-review" tabindex="-1"><a class="header-anchor" href="#event-review">Event Review</a></h2>
<p>The target audience of this event was not serious athletes. It was marketed as a &quot;race&quot;, but it just ended up being a fun event that a received a lot of sign ups because of the HRX and Cult.fit branding and their sales channels. There were obviously no pacers. Post race a lot of people were waiting for the Winner announcement and the Prize Distribution, which obviously never happened, because the race was NOT TIMED!</p>
<p>All in all, it was a very disappointing event and honestly a massive waste of time on a Sunday morning. This time could've been so much better used for any other training session. I will definitely not be running any future renditions of this event.</p>
<h2 id="strava-activity" tabindex="-1"><a class="header-anchor" href="#strava-activity">Strava Activity</a></h2>
<p><a href="https://www.strava.com/activities/10281908567/overview"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1707422024265/05dca468-1b7a-4626-9648-76b3e05251c2.jpeg" alt="HRX 5k strava activity"></a></p>
<h2 id="tips-for-future-runners" tabindex="-1"><a class="header-anchor" href="#tips-for-future-runners">Tips for Future Runners</a></h2>
<p>Dont run this race</p>
]]></content>
    <category term="race-reports"/>
  </entry>
  <entry>
    <title>Bengaluru Midnight Marathon 10K 2023</title>
    <link href="/blog/bengaluru-midnight-marathon-10k-2023/"/>
    <updated>2023-12-16T00:00:00Z</updated>
    <id>/blog/bengaluru-midnight-marathon-10k-2023/</id>
    <content type="html"><![CDATA[<div class="race-info">
<ul>
<li><strong>Event:</strong> Bengaluru Midnight Marathon 10K</li>
<li><strong>Date:</strong> December 16, 2023</li>
<li><strong>Distance:</strong> 10 km</li>
<li><strong>Location:</strong> Bangalore, India</li>
<li><strong>Finish Time:</strong> 51:21</li>
<li><strong>Pace:</strong> ~5:08 min/km</li>
</ul>
</div>
<p>This was a 10k event I ran on 16th December 2023. In hindsight, I probably should've run the 21k instead, as that would've been a good training session at race pace for my TMM training.</p>
<p>The event was sponsored by Akamai (my employer), so my registration was free of cost. The event started around 8:45PM for the 10k, so not the best time to run, but not the worst time either. It was a humid evening and there were no winds.</p>
<p>I met with some of my colleagues at the venue and collected the BIB as well as the race T-Shirt.</p>
<h2 id="the-race" tabindex="-1"><a class="header-anchor" href="#the-race">The Race</a></h2>
<p>I made the mistake of starting at the back, the first 800m were extremely crowded, to the point where I had to slow down to a walking speed. I had to overtake around 600-800 people in the first kilometer.</p>
<p>This was my first timed 10k event, so I was not very sure of what pace I should be targeting, but from previous half marathons and the marathon training, I knew that 50 minutes was an achievable yet challenging target. The &quot;plan&quot; was to maintain 4:50 min/km pace with even splits throughout the race. Due to the crowds, my first km split was 5:33 min. However, the GPS under the Sathya Sai Medical Hospital Metro Station was very dodgy and it made trusting the pace/distance very difficult.</p>
<p>The first 5k of the race went by in a relatively smooth fashion, I was consistently hitting sub 5 min k's, or atleast my watch said so. I also took a gel around 20mins (~4km) into the race, since I wasn't very well hydrated before the race. 6th km onwards, it started getting painful, my km spilts started dipping into 5's and 5:10's. At some point I had side stitches for around a kilometer, but it was at that point in the race of which I barely have any memory of.</p>
<p>My final chip time was 51:21. I am quite certain that, had I started in the front, a sub 50 minute result could've been possible. Nevertheless, given the circumstances it was a good race.</p>
<h2 id="event-review" tabindex="-1"><a class="header-anchor" href="#event-review">Event Review</a></h2>
<p>The event being very &quot;corporate&quot; in nature, was not very competitive. The winners were elite (and fast obviously), but apart from them there weren't many fast athletes which meant for a very long time I had to run alone, and that made keeping the target pace harder. The on-course nutrition was fine, since it was just a 10k, I didn't need a lot of support, just a little bit of water. There were no pacers, which is understandable considering there were very few serious runners.</p>
<p>I might race the event again next year, but definitely not in the 10k category. The longer 21k/42k events start later in the night, making the weather much more conducive to running as well as higher number of serious runners with fewer amateurs.</p>
<h2 id="strava-activity" tabindex="-1"><a class="header-anchor" href="#strava-activity">Strava Activity</a></h2>
<p><a href="https://www.strava.com/activities/10389975044/overview"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1707341731182/b6b87958-b1ae-4ab8-a7ad-5553ca3211b1.jpeg" alt="Strava activity"></a></p>
<hr>
<p><em>Have questions about this race? Feel free to reach out on <a href="https://twitter.com/TheMayankThakur">Twitter</a>.</em></p>
]]></content>
    <category term="race-reports"/>
  </entry>
</feed>
