<?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:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Ring around the repo]]></title><description><![CDATA[Musings on technology, programming, and engineering culture]]></description><link>https://staticfinal.org/</link><image><url>https://staticfinal.org/favicon.png</url><title>Ring around the repo</title><link>https://staticfinal.org/</link></image><generator>Ghost 3.35</generator><lastBuildDate>Wed, 08 Apr 2026 14:06:50 GMT</lastBuildDate><atom:link href="https://staticfinal.org/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[DynamoDB Stream Processing: Scaling it up]]></title><description><![CDATA[<p>This is Part II of the Data Streaming from DynamoDB series. You can read <a href="https://staticfinal.org/ghost/#/editor/5de5a921dcc2595d02c6b30b/">Part I</a> where the primary focus is on a use case where stream processing is helpful(indexing data in ElasticSearch). We evaluated the available options to process streams and discussed in detail how Stream processing can</p>]]></description><link>https://staticfinal.org/dynamodb-stream-processing-scaling-it-up/</link><guid isPermaLink="false">5f7a78657393b43baa8d57bb</guid><category><![CDATA[engineering]]></category><category><![CDATA[aws]]></category><category><![CDATA[stream processing]]></category><dc:creator><![CDATA[Merrin Kurian]]></dc:creator><pubDate>Tue, 03 Dec 2019 00:36:17 GMT</pubDate><content:encoded><![CDATA[<p>This is Part II of the Data Streaming from DynamoDB series. You can read <a href="https://staticfinal.org/ghost/#/editor/5de5a921dcc2595d02c6b30b/">Part I</a> where the primary focus is on a use case where stream processing is helpful(indexing data in ElasticSearch). We evaluated the available options to process streams and discussed in detail how Stream processing can be done using Kinesis Client Library(KCL). In this post we will see how to scale up stream processing for very large throughput. As a use case, we will look at online migration of a Cassandra database to DynamoDB and processing streams to index the same data in ElasticSearch.</p><h1 id="why-scale-up-stream-processing"><strong>Why scale up stream processing?</strong></h1><p>Let us look at a service that has about 250000 active users. They do about 2 million writes to this database. So over the years, the database, currently in Cassandra, has acquired a lot of data. Now, we want to migrate this database to DynamoDB. (Note: To serve 2 million writes a day, one worker seems to be enough to index the data in ElasticSearch in near-real time, as there is no complex processing involved.) Since we cannot take downtime and would like to complete this migration in a reasonable amount of time, we decide to migrate data on a per-user basis from Cassandra to DynamoDB. Now the rate of migration is really dependent on the rate of writes to DynamoDB; so also the writes to ElasticSearch.</p><p>Now, there is a difference between how DynamoDB scales up and how ElasticSearch scales up. We can provision a range of values to autoscale for WCUs to a DynamoDB table, but we setup a cluster with predefined capacity for ElasticSearch. Assuming ElasticSearch is appropriately provisioned to support current and a little into the future of the estimated traffic and data, the data migration use case really boils down to DynamoDB. Again, DynamoDB can scale up to a certain limit(40000 WCUs shared by the table and LSIs and Global tables if any) and so do the corresponding streams. These are soft limits which can be raised by support. Then the rate of migration ultimately comes down to how fast the stream is processed so corresponding data appears in ElasticSearch.</p><p>Imagine that there is only one worker, but DynamoDB writes are at 40000 WCUs( with a few indexes, let us say effective rate is 10000 WCU to the table and therefore the stream). In that case, it will be several hours before the corresponding data appears in ElasticSearch. Moreover, the table continues to get writes, streams continue to grow. After 24 hours streams become unavailable for reads and if the worker is really lagging at that point, there will be data loss. Even in a less dramatic scenario, a service that does 2 million writes a day, provided that the partition key is somewhat uniformly distributed, can benefit from more than a single worker and make use of the parallelism inbuilt to the system. Let’s learn more.</p><h1 id="dynamodb-streams-and-shards">DynamoDB Streams and Shards</h1><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://staticfinal.org/content/images/2019/12/DDB-Table1-1.jpg" class="kg-image" alt><figcaption><em> Courtesy: AWS docs</em></figcaption></figure><p>As shown in the picture above, one DynamoDB partition corresponds to one shard in DynamoDB stream, which can be processed by one KCL worker. So if the table has multiple partitions, stream processing can benefit from multiple workers.</p><h2 id="how-many-workers-do-we-need">How many workers do we need?</h2><p>How do we calculate the number of partitions in a table?<strong><strong> </strong></strong>There are several equations floating around claiming to help calculate this number. Even older AWS documents point to a link which now does not show this calculation. Based on their documentation however we can somewhat guess that a partition cannot process more than 1000 WCUs roughly. Again, I’m not sure how accurate this number is. What has been useful for me is to actually see it in action.</p><p>This is one foolproof way to <strong><strong>find</strong></strong> <strong><strong>the number of shards in the stream, therefore the number of workers</strong></strong> and therefore the number of partitions in the base table. As discussed in <a href="https://staticfinal.org/ghost/#/editor/5de5a921dcc2595d02c6b30b/">Part I of this post series</a>, configure a KCL worker to process the table stream. Then login to AWS console, look for a new table by the name of the worker you have configured. KCL workers checkpoint using a DynamoDB table of the same name and therefore we can see a table that we didn’t create after the worker has started processing stream.</p><p>For a table with min WCU configured as 250(has 3 LSIs), the corresponding worker table has 8 items. There are 6 open shards and 2 that are checkpointed with SHARD_END. LeaseOwner column shows the workerId of the shard that holds the current lease to the shard. LeaseKey is the shardId. So now we know how many shards we have in the stream and therefore how many workers can process the stream in parallel.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://staticfinal.org/content/images/2019/12/Screen-Shot-2019-12-02-at-2.01.44-PM.png" class="kg-image" alt><figcaption>Worker table</figcaption></figure><h1 id="tips-on-scaling-up-writes-and-workers">Tips on scaling up writes and workers</h1><p>Initially I had these configurations for the table:</p><p>RCU: Autoscale. Min: 250 Max: 3000</p><p>WCU: Autoscale. Min: 250 Max: 40000</p><p>What I observed is every time it auto scaled, there was slowness in production performance for 2 minutes. After all this is a live system that does online migrations. Also the number of workers was very small, which do not autoscale. So we started seeing data loss, most likely due to workers not scaling as much as the write throughput on DynamoDB table and stream.</p><p>The other issue was that the DynamoDB table automatically created for a worker doesn’t auto-scale. It has RCU and WCU of 5 when it is created. Soon, reads to this table were getting throttled.</p><p>It is best to do a few trial runs to learn about the nature of throttling and throughput values of the main table, worker table and the efficiency of workers in processing the stream. A few metrics that will help are Throttled write requests and events.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://staticfinal.org/content/images/2019/12/Screen-Shot-2019-12-02-at-3.38.59-PM.png" class="kg-image" alt><figcaption>Throttled writes</figcaption></figure><p>Most often these throttling events don’t appear in the application logs as throttling errors are retriable. So looking at these metrics in AWS console help understand if there is a need to increase WCU for the table. In this case, clearly it will help as requests are throttled by the hundreds every once in a while.</p><p>We can also see metrics from Streams with the number of records returned per batch and the latency in the following:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://staticfinal.org/content/images/2019/12/Screen-Shot-2019-12-02-at-3.42.13-PM.png" class="kg-image" alt><figcaption>Stream metrics</figcaption></figure><p>The other metric to watch out is the capacity on the worker table. As we can see, for this table, the rate at which data is read/written the default values of 5 without autoScaling is no good. WCU required is 50–70.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://staticfinal.org/content/images/2019/12/Screen-Shot-2019-12-02-at-3.41.57-PM-1.png" class="kg-image" alt><figcaption>Worker table metrics</figcaption></figure><h1 id="how-to-prevent-data-loss-and-achieve-near-real-time-processing">How to prevent data loss and achieve near real time processing</h1><p>From our use case to do high throughput online migration, we have a few useful insights. Here in addition to writing to DynamoDB, all data also need to be indexed in near real time in ElasticSearch via KCL workers configured to process DynamoDB streams.</p><p><strong><strong>Create a large number of workers ahead of time: </strong></strong>DynamoDB can auto-scale. Lambda functions can auto-scale. But KCL workers that process streams will not auto-scale. They will continue to process one shard per worker. When DynamoDB autoscales and increases capacity, shards split into two. When shards split, there will be double the number of shards as before and therefore require double the number of workers prior to the split to cover all shards, if the write throughput sustains at large numbers.</p><p>PS1: A worker in KCL is a thread which takes an id. Each worker is uniquely identified by the workerId. KCL configuration allows you to name your workerIds within an application. <a href="https://gist.github.com/mkurian/762d7a0691bd1f8a5f3e055d30cab4cb">Here is the sample code to create ‘multiple workers’. </a></p><p>PS2: If you cannot create enough threads to process the stream in one EC2 instance/JVM, it is beneficial to scale out EC2 instances/JVMs with the same KCL worker configuration, only workerIds need to be different so they are unique across all threads that process the same stream so that the stream shards are distributed correctly. (Similar to Kafka consumer group and consumers). In our case we have a Kubernetes pod for processing DynamoDB stream that we deploy as a ReplicaSet. We can spin up more workers if we need to across multiple EC2 instances by increasing the number of replicas.</p><p><a href="https://gist.github.com/mkurian/762d7a0691bd1f8a5f3e055d30cab4cb">In the sample code</a>, there are 9 <em><em>worker</em></em> threads are created with application <em><em>fooWorker</em></em>. Each worker is uniquely identified by the workerId. One <em><em>streamWorker</em></em> can process a single shard. Maximum throughput for processing streams to achieve near real time processing is by configuring same number of workers as the number of shards. More workers will sit idle. Having more workers is not a bad idea if there is a chance for the table/stream scale out further.</p><p><strong><strong>Configure DynamoDB table to use high WCU from the beginning: </strong></strong>We did see data loss in ElasticSearch during our test runs, possibly due to workers not catching up fast enough and a few shards that were never processed. In order to avoid running into this uncertainty, if we anticipate sustained high throughput over a long period of time, it is better to provision that table with the large throughput than to wait for it to be throttled and thereafter autoscale. While autoscaling shards split up and increase in number. Since migration is only going to take a finite amount of time, we can afford to have the high WCUs provisioned on the table, instead of having to worry about proving that all data migrated correctly, the latter being much harder.</p><p><strong><strong>Configure auto-scaling for the worker table: </strong></strong>The <em><em>fooWorker </em></em>KCL application in this case will create a <em><em>fooWorker</em></em> DynamoDB table for checkpointing. This table needs to have higher capacity than the defaults with which it gets automatically created. Ensure that you either provision the required capacity or enable autoScaling/onDemand so stream workers are not throttled while processing streams.</p><p>With these settings enabled, ElasticSearch indexes are being updated mostly near real time. We are able to verify that all data eventually reaches ElasticSearch without significant lag and that there is no data loss.</p><p>That brings us to the end of 2 part series for processing DynamoDB Streams for near real time indexing in ElasticSearch at high throughput. </p>]]></content:encoded></item><item><title><![CDATA[DynamoDB Stream Processing]]></title><description><![CDATA[<p>DynamoDB Streams makes change data capture from database available on an event stream. One of the use cases for processing DynamoDB streams is to index the data in ElasticSearch for full text search or doing analytics. In this post, we will evaluate technology options to process streams for this use</p>]]></description><link>https://staticfinal.org/stream-processing-ddb/</link><guid isPermaLink="false">5f7a78657393b43baa8d57ba</guid><category><![CDATA[engineering]]></category><category><![CDATA[aws]]></category><category><![CDATA[stream processing]]></category><dc:creator><![CDATA[Merrin Kurian]]></dc:creator><pubDate>Tue, 03 Dec 2019 00:16:19 GMT</pubDate><content:encoded><![CDATA[<p>DynamoDB Streams makes change data capture from database available on an event stream. One of the use cases for processing DynamoDB streams is to index the data in ElasticSearch for full text search or doing analytics. In this post, we will evaluate technology options to process streams for this use case. In <a href="https://staticfinal.org/ghost/#/editor/5de5ad34dcc2595d02c6b31e/">a subsequent post</a>, we will dive into details on scaling up the stream processing, if this approach is followed.</p><h2 id="dynamodb-streams"><strong>DynamoDB Streams</strong></h2><p>Enable DynamoDB Streams in the table specification</p><p><strong><strong>"StreamSpecification"</strong></strong>: {<br>    <strong><strong>"StreamEnabled"</strong></strong>: <strong><strong>true</strong></strong>,<br>    <strong><strong>"StreamViewType"</strong></strong>: <strong><strong>"NEW_AND_OLD_IMAGES"</strong></strong><br>}</p><p>Note: If you are planning to use GlobalTables for DynamoDB, where a copy of your table is maintained in a different AWS region, <strong><strong>“NEW_AND_OLD_IMAGES” </strong></strong>needs to be enabled.</p><p>After streams are enabled on a table, the streamArn is required to configure a client application to process streams. It will look like this:</p><p><strong><strong>"arn:aws:dynamodb:{aws-region}:{aws-account-number}:table/{table-name}/stream/2019-11-07T20:49:20.459"</strong></strong></p><p><a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Streams.html" rel="noopener nofollow">More on how table activity is captured on DynamoDB Streams</a></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://staticfinal.org/content/images/2019/12/streams-terminology.png" class="kg-image" alt><figcaption><em> Courtesy: AWS Docs</em></figcaption></figure><h1 id="2-approaches-to-process-streams">2 approaches to process streams</h1><h2 id="serverless-approach-">Serverless approach:</h2><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://staticfinal.org/content/images/2019/12/DDB_ES-Lambda--1-.jpeg" class="kg-image" alt><figcaption>Lambda function Approach to process streams and index data</figcaption></figure><p>The easiest approach to index data from DynamoDB into ElasticSearch for example is to enable a Lambda function, as documented here: <a href="https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-aws-integrations.html#es-aws-integrations-dynamodb-es" rel="noopener nofollow">https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-aws-integrations.html#es-aws-integrations-dynamodb-es</a></p><p>There are several reasons why <strong><strong>I</strong></strong> <strong><strong>do not prefer a Lambda function</strong></strong> for our use case. Some of them are:</p><ol><li>Deployment complexity: We run our services in Kubernetes pods, one for each type of application. Adding in a lambda function/serverless will change the deployment topology and bring in more complexity to our deployment automation.</li><li>Observability: The only way to observe what happens inside a Lambda function is to use CloudWatch service. We already have a different stack of observability framework to use and analyze information from application logs and would like to continue to leverage that. If we decide to use Lambda function, we need to capture logs from Cloudwatch and publish them to s3 buckets to push to the stack.</li><li>Skill set of the team: We are primarily application engineers who switch to DevOps mode when needed. We prefer to work with client libraries in java/kotlin compared to other languages/tools/frameworks for production systems that we need to maintain as a team of 3 engineers.</li></ol><p>Here are the reasons why AWS advocates use of Lambda function:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://staticfinal.org/content/images/2019/12/DDB-Table1.jpg" class="kg-image" alt><figcaption><em> Courtesy: AWS Docs</em></figcaption></figure><ol><li>Ability to autoscale stream processing. Unless you have a really large workload and really complicated processing, lambda functions would work. There is no need to make additional effort to scale up stream processing.</li><li>CloudWatch metrics: All metrics go to CloudWatch and that should help with observability if you already have that built in place.</li><li>Limitation on throughput: There is a 100 record per shard limit on how many records are processed at a time. KCL workers allow more throughput per batch based on what I heard.</li></ol><h2 id="hosted-service-approach-">Hosted Service approach:</h2><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://staticfinal.org/content/images/2019/12/DDB_ES-KCL-worker-high-level.jpeg" class="kg-image" alt><figcaption>KCL worker with DynamoDB Adapter</figcaption></figure><p>Since we ruled out Lambda function, the other approach is to use KCL(Kinesis Client Library) worker with DynamoDB Adapter for processing DynamoDB streams. Since we are building java/kotlin services and are primarily application developers, this option is better aligned with the skill set of the team for long term maintainability of the stack.</p><p>In this case an application is built around KCL with DynamoDB Adapter, that creates a worker configured to listen to changes to the stream and process them.</p><p>The <strong><strong>disadvantage</strong></strong> with using KCL workers is that we need to scale up workers on our own based on performance requirements in processing the stream. More about that in the upcoming post. The <strong><strong>advantage</strong></strong> is that it is really another application deployed alongside your main service and you can leverage your existing deployment infrastructure(a separate pod on a Kubernetes cluster), code infrastructure(Springboot application) and the telemetry/observability stack you are already familiar with for logging and troubleshooting.</p><p>Stream processing requires KCL to instantiate a worker. We must provide the worker with configuration information for the application, such as the stream arn and AWS credentials, and the record processor factory implementation.</p><p>As mentioned in the documentation, the worker performs the following tasks. For most cases, we don’t have to tweak any of these settings. It is good to know that these are the activities happening behind the scenes. The worker:</p><ul><li>Connects to the stream.</li><li>Enumerates the shards within the stream.</li><li>Coordinates shard associations with other workers (if any).</li><li>Instantiates a record processor for every shard it manages.</li><li>Pulls records from the stream.</li><li>Pushes the records to the corresponding record processor.</li><li>Checkpoints processed records.</li><li>Balances shard-worker associations when the worker instance count changes.</li><li>Balances shard-worker associations when shards are split.</li></ul><p>DynamoDB writes data into shards(based on the partition key). Each shard is open for writes for 4 hours and open for reads for 24 hours. Essentially, KCL worker will subscribe to this stream, pulls records from the stream and pushes them to the record processor implementation that we will provide. KCL will allow a worker per shard and the data lives in the stream for 24 hours. These are important limits to remember. We will discuss throughput and latency of stream processing in a bit.</p><p><strong><strong>Worker configuration</strong></strong></p><p>So far we know that we need a KCL worker with the right configuration and a record processor implementation that processes the stream and does the checkpointing. How do we actually go about doing it?</p><p>Let’s say we have 4 DynamoDB tables whose data need to be indexed in ElasticSearch. Each table produces a stream, identified by the streamArn. Now we need KCL 4 workers, one each for each stream. <a href="https://gist.github.com/mkurian/78c9d9fd53c56cbf732c91a16bfadc9d">Here is a sample.</a> Most values can be left as defaults, except the AWS credentials and the identifiers of stream and worker.</p><p><strong><strong>StreamRecordProcessor implementation</strong></strong></p><p>KCL requires us to provide a StreamRecordProcessorFactory<strong><strong> </strong></strong>implementation to actually process the stream. Details in the docs: <a href="https://docs.aws.amazon.com/streams/latest/dev/kinesis-record-processor-implementation-app-java.html" rel="noopener nofollow">https://docs.aws.amazon.com/streams/latest/dev/kinesis-record-processor-implementation-app-java.html</a></p><p>Provide implementations for IRecordProcessor and IRecordProcessorFactory. Refer <a href="https://github.com/aws/aws-sdk-java/blob/master/src/samples/AmazonKinesis/AmazonKinesisApplicationSampleRecordProcessor.java" rel="noopener nofollow">https://github.com/aws/aws-sdk-java/blob/master/src/samples/AmazonKinesis/AmazonKinesisApplicationSampleRecordProcessor.java</a></p><p><strong><strong>override fun </strong></strong>processRecords(processRecordsInput: ProcessRecordsInput) {<br>   <br>    processRecordsWithRetries(processRecordsInput.<em><em>records</em></em>)<br>    <br>    checkpoint(processRecordsInput);<br>}</p><p><strong><strong><em><em>processRecordsWithRetries</em></em></strong></strong><em><em>: </em></em>This is where the stream processing logic will live. In our specific case, we will generate an id for the document based on the keys in DynamoDB table and create an index/delete request in ElasticSearch. Note that it is advantageous to use the Bulk indexing in ElasticSearch to reduce roundtrip time thereby increasing throughput and reducing latency for data to appear in ElasticSearch. At the rate of indexing a few hundred records every second, I have seen them appear in ElasticSearch within 200 ms. Note that, KCL absorbs any exception thrown from the processRecords and moves forward to process next batch of events. So it is really critical to have an effective exception handling strategy, one that retries for retry-able errors(intermediate technical glitches) and another for handling non-retry-able errors(eg. invalid document wrt ElasticSearch mapping).</p><p><strong><strong><em><em>checkPoint</em></em></strong></strong><em><em>: </em></em>This is the mechanism used by the KCL worker to keep track of how much data from the stream has been read by the worker. So in case worker terminates/application restarts, it will catch up from the point where it was last checkpointed in the stream. This is similar to committing offsets in Kafka.</p><h2 id="code-samples-references">Code samples &amp; References</h2><p>AWS documentation on using KCL to process DynamoDB Stream is here: <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Streams.KCLAdapter.html" rel="noopener nofollow">https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Streams.KCLAdapter.html</a></p><p>Here is some sample code from the docs that get one started on the record processing:</p><p><a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Streams.KCLAdapter.Walkthrough.html" rel="noopener nofollow">https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Streams.KCLAdapter.Walkthrough.html</a></p><h2 id="throughput-and-latency"><strong>Throughput and Latency</strong></h2><p>What we have done so far will create a single worker to process the stream. What if that is not enough? We can determine if we need more worker threads based on the amount of writes to both DynamoDB and ElasticSearch. There are 2 ways to compare:</p><ul><li>Analyze the number of DynamoDB writes per minute and compare that to ElasticSearch writes.</li><li>Instrument logging to trace a single record through the entire pipeline, both DynamoDB and ElasticSearch. So monitoring a single item can also provide data on how much lag is there for a record to move from DynamoDB to ElasticSearch.</li></ul><p>If the application writes to DynamoDB a few hundred records at a time, usually 1 worker is probably enough. It also depends on how distributed the partition key is. Let’s say we found that it takes several minutes for the data to appear in ElasticSearch once it is written in DynamoDB. In such a case, the first parameter to examine is <em><em>streamConfig.batchSize</em></em> in the configuration above.</p><p><strong><strong>KinesisClientLibrary::maxRecords</strong></strong></p><p>If your application writes thousands of Items to DynamoDB, there is no point in keeping <em><em>maxRecords</em></em> low, eg. 100. A high number (default: 1000) will definitely improve the throughput and therefore latency of your data appearing in ElasticSearch. There is no reason to lower this value for most cases.</p><p>Now, there will be cases when you have high throughput writes (ie. several thousand writes per second) on your DynamoDB tables. In such cases a single worker is not going to be enough. We will discuss scaling up stream processing using KCL workers in <a href="https://staticfinal.org/ghost/#/editor/5de5ad34dcc2595d02c6b31e/">the next post in this series</a>.</p>]]></content:encoded></item><item><title><![CDATA[Platform Migration is hard – Data Migration is even harder]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>In my last <a href="https://staticfinal.org/application-rewrite/">blog</a>, I talked about application rewrites. In this post, I would like to focus on Platform rewrite, which is a flavor of application rewrite. The motivation for 'Platform rewrite' could be many. This might include:</p>
<ul>
<li>A decision to switch from monolithic architecture to micro-services. For this post,</li></ul>]]></description><link>https://staticfinal.org/platform-migration-is-hard-data-migration-is-even-harder/</link><guid isPermaLink="false">5f7a78657393b43baa8d57b9</guid><category><![CDATA[engineering]]></category><category><![CDATA[software]]></category><category><![CDATA[migration]]></category><dc:creator><![CDATA[George Chiramattel]]></dc:creator><pubDate>Sun, 31 Mar 2019 01:06:02 GMT</pubDate><media:content url="https://staticfinal.org/content/images/2019/03/dataMigration.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://staticfinal.org/content/images/2019/03/dataMigration.jpg" alt="Platform Migration is hard – Data Migration is even harder"><p>In my last <a href="https://staticfinal.org/application-rewrite/">blog</a>, I talked about application rewrites. In this post, I would like to focus on Platform rewrite, which is a flavor of application rewrite. The motivation for 'Platform rewrite' could be many. This might include:</p>
<ul>
<li>A decision to switch from monolithic architecture to micro-services. For this post, I consider this as a Platform Migration.</li>
<li>This could include a decision to change the storage architecture. Like a decision to migrate from an RDBMS store to a NoSQL store. That, in my opinion, is an example of Platform rewrite that includes Data Migration.</li>
</ul>
<h3 id="whatisinvolved">What is involved?</h3>
<p>Let's look at what is involved in Platform and Data migration</p>
<h4 id="platformmigration">Platform Migration</h4>
<p>If we are able to reproduce the current snapshot of the API surface and its behavior in the target system, then we can say that the bulk of platform migration is done. We can rerun integration tests after wiring in the new system to ensure correctness.</p>
<h4 id="datamigration">Data Migration</h4>
<p>As a system matures, its API and business logic evolve. For business logic migration to be considered complete, we have to faithfully capture the latest snapshot of this system. The storage system is where all these historical changes still exists. Think of this like layers of sediments at the bottom of a lake. The data migration task has to deal with all these layered sediments.</p>
<p>This would mean that, data-migration is where the team will spend the most amount of time. Let me explain this is detail.</p>
<p>When we attempt a data migration, a significant portion of it will succeed. But a significant subset will fail. The team will have to analyze the failures, fix current code to accommodate for that and run the migration again. This is repeated many times till <strong>all</strong> data is migrated.</p>
<p><img src="https://staticfinal.org/content/images/2019/03/bouncingBall.png" alt="Platform Migration is hard – Data Migration is even harder"></p>
<p>The best way to think of it is to imagine dropping a bouncing ball. The distance it travels is not the height of the ball when it was dropped. It should also include the sum of all the bounces that happened before the ball comes to total rest.</p>
<h3 id="whyisthissignificant">Why is this significant?</h3>
<p>The hidden cost of data migration can cause us to significantly underestimate the time required to switch to the new system. During the transition period - new features should be released to both the classic system and the new system simultaneously.</p>
<h3 id="biggestarchitecturalconcern">Biggest Architectural concern</h3>
<p>To me, architecture is about modeling a system such that, it continues to remain malleable to change. In this view, 'architectural concerns' is that <em>thing</em> that is the most difficult to change. It is common to see architectural concerns shift through the lifetime of the application. 'Tight coupling' and hard to change 'external dependencies' are early stage architectural concerns.</p>
<p>Most <em>successful</em> enterprise applications, over time collect a lot of data. It can grow to a point that it becomes the most difficult thing to change. So, over time, the size of the data becomes the biggest <em>architectural concern</em>. This is reflected in the fact that any significant change to that (migration for example) is really hard to do.</p>
<h3 id="hierarchyofconcerns">Hierarchy of concerns</h3>
<p>In our team, we keep the above point in mind when we go about making 'architectural' decisions. Most applications can easily go through a UI refresh - and it should take relatively small amount of time to finish. Look at the pace of change in front-end development. Frameworks and libraries change very often.<br>
Business logic layer is tougher to change. Nothing to be trivialized - but doable in reasonable amount of time.<br>
For a system that has grown to be relatively big, data migration can take the longest amount of time. Keep this in mind when we take decisions. Think about your data very carefully. When we start off a new project, change in data structure is relatively easy. Not so, as data becomes bigger. And this realization should influence our hierarchy of concerns.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Application Rewrite]]></title><description><![CDATA[<p>In my experience, every successful application will eventually reach a stage, when it becomes worthwhile contemplating, if it is a better investment to continue to improve on the existing codebase or to rewrite the whole application. The gravity of this decision is huge, as the consequence of getting this wrong</p>]]></description><link>https://staticfinal.org/application-rewrite/</link><guid isPermaLink="false">5f7a78657393b43baa8d57b8</guid><category><![CDATA[engineering]]></category><category><![CDATA[software]]></category><dc:creator><![CDATA[George Chiramattel]]></dc:creator><pubDate>Thu, 28 Mar 2019 18:35:25 GMT</pubDate><media:content url="https://staticfinal.org/content/images/2019/03/background-bit-business-2004161--1-.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://staticfinal.org/content/images/2019/03/background-bit-business-2004161--1-.jpg" alt="Application Rewrite"><p>In my experience, every successful application will eventually reach a stage, when it becomes worthwhile contemplating, if it is a better investment to continue to improve on the existing codebase or to rewrite the whole application. The gravity of this decision is huge, as the consequence of getting this wrong can be disastrous. Many experts have <a href="https://www.joelonsoftware.com/2000/04/06/things-you-should-never-do-part-i/">pointed out</a> that this exercise is not even worth considering. It is good to learn from projects that succeeded or failed. Every project is unique and patterns that work in one organization might not translate well into other companies. But it is worthwhile looking at the available literature. Here are some:</p><ul><li><a href="https://medium.com/@herbcaudill/lessons-from-6-software-rewrite-stories-635e4c8f7c22">Lessons from 6 software rewrite stories</a></li><li><a href="https://labs.spotify.com/2019/03/25/building-spotifys-new-web-player/">Building Spotify’s New Web Player</a></li><li><a href="https://signalvnoise.com/posts/3856-the-big-rewrite-revisited">The Big Rewrite, revisited</a></li></ul><h2 id="why-should-we-consider-the-option-of-rewrite">Why should we consider the option of rewrite?</h2><p>Software is supposed to be infinitely malleable - so we should be able to change any piece of software -right? And only successful software becomes legacy - so that is valuable as well. And, if the current system is not broken, why fix it? All these are important considerations while contemplating on the question of application rewrite. </p><p>Also, I want to draw a distinction between refactoring and rewriting. All well managed products should invest in constant refactoring of its codebase. Failure to do so will result in reduced shelf life of the application and making it more ripe for rewrite. In spite of constant refactoring, there could be many reasons to consider an application rewrite. Some of the most common reasons are:</p><ul><li>Existing code has become too difficult to handle and extend. Even simple changes have cascading unintended consequences. This makes it difficult to add new features and increases the cost of maintaining the current codebase. This could result in a system that is changing slower than its competition. Even for a market leading application, this could mean slow death as <a href="https://web.archive.org/web/20151212054843/https://www.inc.com/magazine/20091101/does-slow-growth-equal-slow-death.html">slow growth equal slow death</a>.</li><li>It becomes increasing difficult to motivate newer engineers. General enthusiasm decreases. If a happy team delivers good products, the opposite is also true.</li><li>As the product evolves, design decisions that were appropriate for that time could become a liability now (historical design mistakes) - Invariants that are built into the system makes it near impossible to repurpose the system, when we find newer approaches.</li></ul><h2 id="let-s-look-at-some-reasons-why-software-rewrites-fail">Let’s look at some reasons why software rewrites fail</h2><p>These are some high level patterns that we have to keep in mind:</p><ul><li><strong>Under Estimate effort</strong>: The crufty-looking parts of the application’s codebase often reflect corner cases and weird bugs. An immature  approach to estimation can overlook the many man-years of development that went into creating the current software. Underestimating the project can lead to the initiative failing because of cost overruns. </li><li><strong>Setup for failure</strong>: For a project of this magnitude, the team should start off by setting appropriate expectations with the business stakeholders. The team should <strong>not</strong> fall into the trap of over promising to justify the cost of the rewrite. </li><li><strong>Morale effects</strong>: A project rewrite can split the current team. Given that domain knowledge is not evenly distributed among team members, splitting up the team can cause resource issues that are not anticipated.</li><li><strong>Elitist mentality</strong>: This can also have the unintended consequence of splitting up the team into cool guys and legacy members.</li></ul><!--kg-card-begin: markdown--><ul>
<li><strong>Unrealistic Constraints</strong>: Usually, software rewrites are sold to business stakeholders with the expectation that, at the end, the current system can be switched off and all users will be on the newer system. This way of positioning the rewrite will setup the initiative for failure because of the following constraints:
<ul>
<li><strong>Feature parity</strong>: The project is never done until the target system achieves feature parity with the current system. This will apply for feature that might not have aged well, but customers have grown used to. Customers have grown used to a certain way of working in the current system and they don't want their cheese to be moved. This hinders innovation on the target architecture.
<ul>
<li>If you are not in a saturated market, and if the current product does not appeal to a new-generation user, then sticking to existing features and workflows to satisfy the current user-base will guarantee that the product will die in obsolesce. “Golden Hand-shake”</li>
</ul>
</li>
<li><strong>Moving target</strong>: Usually massive rewrites are multi-year initiatives and the current product continues to evolve. This makes the goal of achieving feature parity that much more difficult.</li>
<li><strong>Less resources on existing product</strong>: Splitting the team also results in lower resources across both existing and newer initiative.</li>
<li><strong>High burden of correctness</strong>: This is easily overlooked aspect. The current product has benefitted from incremental releases are tested and validated in the field. And corner cases are fixed. A completely new system with all the existing feature implemented new has to short-circuit the otherwise evolutionary release process. The entire release has to be correct on day one. Which in reality will never happen. This means that at launch time, the system will be perceived as defective and less stable.</li>
<li><strong>Migrate Data</strong>: In my opinion, this is the biggest cost of rewrites. This will be the topic of a new post. Watch this space.</li>
</ul>
</li>
</ul>
<!--kg-card-end: markdown--><h2 id="possible-solutions">Possible Solutions</h2><p>In my opinion, the best way to fight product obsolescence is to disrupt the current product from within. Instead of rewriting the current product, create something innovative in a related category. This is similar to what BaseCamp did. Instead of sunsetting BaseCamp, the team committed to perpetually supporting current product as BaseCamp Classic and they launched BaseCamp 2. There was no guarantees on upgrades. Existing users can stay and will be fully supported. While doing rewrite, its best not to burden yourself with existing users. This gives the team immense freedom:</p><ul><li>Freedom to drop features without backlash from current users. Not constrained by influential existing customers.</li><li>Freedom to launch Minimum Viable Product (no need for feature parity). Launch a new product optimized for current non-consumption and learn from it.</li><li>And above all, freedom to <strong>not </strong>migrate data</li></ul><p>For BaseCamp, this model was so successful that they repeated this pattern and created BaseCamp 3 as well. Another industry example is the case of Visual Studio and Visual Studio Code. </p><p>Gmail team did something slightly differently. They came up with ‘Inbox’, a different take on Gmail. But, they decided to keep the same backend. This meant that users could potentially switch between these two experiences. And also, they eliminated the need for data migration (remember, that is certainly a big burden). But, this also put significant constraints on the amount of change they can introduce. </p><h2 id="conclusion">Conclusion</h2><p>Enterprise application rewrite is non-trivial. You get better success with launching a new product that is optimized for new customers. It might make sense to split features into micro-services and let the newer product use that. This also gives an opportunity to add these capabilities to the classic product and avoid data migration.</p>]]></content:encoded></item><item><title><![CDATA[Project Lombok & Spring's @Qualifer annotation]]></title><description><![CDATA[<p>When <a href="https://twitter.com/hoserdude">@hoserdude</a> &amp; I stood up QuickBooks Self-Employed's backend almost 4 years ago, Spring Boot was the new shiny thing offering RoR type productivity in the Java world. We fell in love with Spring's Dependency Injection via the <code>@Autowired</code> annotation - we used the annotation liberally, primarily on fields. I</p>]]></description><link>https://staticfinal.org/project-lombok-spring-qualifier/</link><guid isPermaLink="false">5f7a78657393b43baa8d57b7</guid><category><![CDATA[engineering]]></category><category><![CDATA[software]]></category><category><![CDATA[spring]]></category><category><![CDATA[dependencyinjection]]></category><category><![CDATA[random]]></category><dc:creator><![CDATA[Shrisha Radhakrishna]]></dc:creator><pubDate>Mon, 18 Feb 2019 21:04:15 GMT</pubDate><content:encoded><![CDATA[<p>When <a href="https://twitter.com/hoserdude">@hoserdude</a> &amp; I stood up QuickBooks Self-Employed's backend almost 4 years ago, Spring Boot was the new shiny thing offering RoR type productivity in the Java world. We fell in love with Spring's Dependency Injection via the <code>@Autowired</code> annotation - we used the annotation liberally, primarily on fields. I sold many developers on this technique until we started seeing more &amp; more instances of class bloat and developers inadvertently creating instances in invalid state<a href="https://twitter.com/odrotbohm">s.</a></p><p><a href="https://twitter.com/odrotbohm">@odrotbohm</a>'s "Why field injection is evil" <a href="http://olivergierke.de/2013/11/why-field-injection-is-evil/">post</a> explains the issues really well.</p><p>Anyway, I'm a convert now and avoid field injection. Even though our codebase is 4+ years old, I want to think we've been diligent about paying off tech debt and keeping dependencies, patterns, and designs up to date. Thanks to in no small part to <a href="https://twitter.com/lerocha">@lerocha</a>. However, we had a few nasty @Components that Autowired <code>@Qualifier</code> fields in; moving these classes to constructor injection meant hand-writing constructors as Lombok's <a href="https://projectlombok.org/features/constructor">made-to-order-constructors feature </a>didn't deal with Qualified beans. The thought of not being able to leverage the boilerplate-reducing abilities of Lombok meant developers stuck with field injection and in fact the pattern perpetuated.</p><p>I rejoiced when I saw Lombok's <code>copyableAnnotation</code> feature in <code>1.18.4</code>. it was as simple as adding a <code>lombok.config</code> in the project root with:</p><!--kg-card-begin: markdown--><p><code>lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Qualifier</code></p>
<!--kg-card-end: markdown--><p>Boom! We are now able to move all these legacy classes to constructor injection. No excuses!</p><p>This <a href="https://ath3nd.wordpress.com/2018/12/13/spring-lombok-or-injection-just-became-a-bit-easier-part-2-of-2/">post</a> does a nice job of explaining via an example.</p>]]></content:encoded></item><item><title><![CDATA[The role of a Software Engineering Manager]]></title><description><![CDATA[<figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://images.unsplash.com/photo-1531482615713-2afd69097998?ixlib=rb-1.2.1&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=2000&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ" class="kg-image" alt srcset="https://images.unsplash.com/photo-1531482615713-2afd69097998?ixlib=rb-1.2.1&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=600&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ 600w, https://images.unsplash.com/photo-1531482615713-2afd69097998?ixlib=rb-1.2.1&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1000&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ 1000w, https://images.unsplash.com/photo-1531482615713-2afd69097998?ixlib=rb-1.2.1&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1600&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ 1600w, https://images.unsplash.com/photo-1531482615713-2afd69097998?ixlib=rb-1.2.1&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=2400&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ 2400w" sizes="(min-width: 720px) 720px"><figcaption>Photo by <a href="https://unsplash.com/@nesabymakers?utm_source=ghost&utm_medium=referral&utm_campaign=api-credit">NESA by Makers</a> / <a href="https://unsplash.com/?utm_source=ghost&utm_medium=referral&utm_campaign=api-credit">Unsplash</a></figcaption></figure><p>This is an opinionated view based on my observations as an Engineer and a people leader in sizable organizations over the past couple of decades. I have had the privilege of learning from great managers &amp; colleagues. I have also run away from toxic</p>]]></description><link>https://staticfinal.org/the-role-of-a-software-engineering-manager/</link><guid isPermaLink="false">5f7a78657393b43baa8d57b6</guid><category><![CDATA[engineering]]></category><category><![CDATA[software]]></category><category><![CDATA[manager]]></category><dc:creator><![CDATA[Shrisha Radhakrishna]]></dc:creator><pubDate>Mon, 18 Feb 2019 06:25:51 GMT</pubDate><content:encoded><![CDATA[<figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://images.unsplash.com/photo-1531482615713-2afd69097998?ixlib=rb-1.2.1&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=2000&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ" class="kg-image" alt srcset="https://images.unsplash.com/photo-1531482615713-2afd69097998?ixlib=rb-1.2.1&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=600&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ 600w, https://images.unsplash.com/photo-1531482615713-2afd69097998?ixlib=rb-1.2.1&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1000&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ 1000w, https://images.unsplash.com/photo-1531482615713-2afd69097998?ixlib=rb-1.2.1&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1600&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ 1600w, https://images.unsplash.com/photo-1531482615713-2afd69097998?ixlib=rb-1.2.1&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=2400&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ 2400w" sizes="(min-width: 720px) 720px"><figcaption>Photo by <a href="https://unsplash.com/@nesabymakers?utm_source=ghost&utm_medium=referral&utm_campaign=api-credit">NESA by Makers</a> / <a href="https://unsplash.com/?utm_source=ghost&utm_medium=referral&utm_campaign=api-credit">Unsplash</a></figcaption></figure><p>This is an opinionated view based on my observations as an Engineer and a people leader in sizable organizations over the past couple of decades. I have had the privilege of learning from great managers &amp; colleagues. I have also run away from toxic environments that sucked the happiness out of everyone including the people producing the toxins.</p><p>If someone tells you that they have complete clarity on the role of an Engineering Manager, they would be lying. Simply put, this is one the toughest roles out there - one that requires you to operate at the intersection of people, process, product, and technology.</p><h2 id="team">Team </h2><h3 id="create-an-environment-of-fun-learning-collaboration">Create an environment of fun, learning, &amp; collaboration</h3><p>This is hard work. Great teams love <strong>teaching</strong>, are <strong>diverse</strong>, and every team member <strong>has each other’s back</strong>. If you haven’t already read Google’s research on what makes teams great, I encourage you to <a href="https://rework.withgoogle.com/blog/five-keys-to-a-successful-google-team/">check it out</a>. As managers, we’ll have to have a pulse on the overall strengths and weakness of the team so you can intervene and course correct appropriately. Be it a tactical thing like offering training to the more nuanced forming of groups based on complementary skills.  Building trust across your team is foundational to creating a fun, learning and collaborative environment.  Creating a place where learning and failing are both recognized (positively) is essential.  </p><h3 id="hire-and-develop-diverse-talent">Hire and develop diverse talent </h3><p>Hiring is a critical component of building culture and great teams. You immerse yourself in the hiring process and look for people who can augment the culture, not just be “culture fits”. Looking for <strong>eager learners</strong> that bring passion and energy to the interview process is something you strive to do. </p><h3 id="teaching-to-fish-instead-of-fishing">Teaching to fish instead of fishing</h3><p>Too often we get ourselves into situations where any work related to “X” always goes to one person who’s heavily knowledgeable in an area. While this ends up delivering the fastest outcome for the task, we end up doing ourselves a disservice in at least three ways:</p><ol><li>We burden that person with always “owning” that particular area, which can put a strain on the person and can lead to somewhat reduced code quality over time (b/c only 1 person owns that area, hence they don’t have any ‘qualified’ reviewers)</li><li>We lose the opportunity to teach other engineers (who are often hungry for learning) about that area/technology.</li><li>That person burns out and/or loses all hope of gaining new knowledge.</li></ol><h2 id="individuals">Individuals</h2><p>I do not doubt for one second that routine processes to check in with our Engineers help. Monthly check-ins, 1:1s, etc are all valuable. But what’s most important is if you know what drives Jane and what’s stopping Joe from excelling. To get to this, I believe a few important things need to be talked about:</p><ul><li>Ensure every employee is taking on <strong>meaningful</strong>, and <strong>impactful</strong> work. If you find yourself just allocating work to someone, stop and look for underlying issues. Note that the definition of <em>meaningful</em> varies by level: A Junior Engineer on his first job may be super excited to take on updating content copy in a workflow whereas a more Senior Engineer may be looking for something that’s end-to-end (full-stack, perhaps) to broaden her horizon.</li><li>In your 1:1s, you ask specific questions.</li></ul><blockquote>What’s something that everyone is afraid of talking about?</blockquote><blockquote>What could we have done differently in project X?</blockquote><blockquote>What advice do you have for me before we start this new thing?</blockquote><ul><li>You focus on personal development goals. Public speaking, creating tech presentations, opportunity to be mentors are all laudable goals and you are in a unique position to broker connections.</li></ul><h3 id="servant-leadership">Servant leadership</h3><p>When you are passionate on behalf of your Engineers, whether it be for the project, for developer productivity, or for their own time to “do what’s right”, this creates so much trust and appreciation.</p><h3 id="talkers-vs-doers-what-you-recognize-reward-and-praise-matters">Talkers vs. Doers: what you recognize, reward, and praise matters </h3><p>A big component of a healthy culture relies on you keeping the folks who “talk more and do less” under control. In order to do this, you have to get a pulse of `real` work that is happening. 🗣</p><p>As teams grow and charters expand, it’s easy to encourage (and even rely on) story-telling, to a fault. And if that behavior gets rewarded (because you are unable to distinguish between what is being said vs. done), this can be a big drain on the entire team.</p><h2 id="your-peers">Your peers</h2><p>For us and the initiatives we undertake to succeed, we need to help our peers. We lean on each other and continually learn from each other. Give each other candid feedback, call each other out, bring contentious topics to your group to discuss - but, avoid talking behind people’s backs.</p><p>Remember that your <em><strong>Team #1</strong></em> is comprised of your peers.</p><figure class="kg-card kg-embed-card"><iframe src="https://player.vimeo.com/video/219540743?app_id=122963" width="640" height="360" frameborder="0" title="Team #1" allow="autoplay; fullscreen" allowfullscreen></iframe></figure><h2 id="process">Process</h2><ul><li>You strive to create processes that add value and don’t become overhead. </li><li>You acknowledge that what works and is needed today may become obsolete tomorrow.</li><li>You try to create processes in a way that allows Engineers to focus on the most important part of their job: Building. Creating. Designing. 15 minute blocks of coding amounts to nothing. Four 1-hour blocks in a day are not bad, a solid 4-hour block is great! ⏱</li><li>Processes and the environment allow junior engineers to have singular focus while expecting more senior devs to multi-task and context-switch.</li><li>You do not create meetings in order for you to remain informed. You remain informed by sitting with your engineers and the working team. </li><li>You empower teams to make decisions - but, you're not afraid to break ties.</li><li>You create a culture where people disagree, but don’t get stuck debating. You help make decisions.</li><li>You embrace the <em><strong>“It’s not my fault, but it’s my problem”</strong></em> mindset. (e.g., you take on a bug fix in a code repo that you don’t “own”).</li></ul><h2 id="product">Product</h2><ul><li>Use your products. As an employee previously in a telecom company and now a Small Business software company, this is easier said than done. Not just the product that you and your engineers work on.</li><li>Report bugs. Follow up.</li><li>Play with competitors' products.</li></ul><h2 id="technology">Technology</h2><p>These days, frameworks are a dime a dozen and the technology landscape is changing rapidly. You simply cannot build expertise in all the areas where your Engineers are involved. My recommendation is to pick an area in a given month (if a month is too short, make it a quarter) and go as deep as you can. If you only stay broad, you’ll find it harder to stay in touch with the code and the decisions, imho. </p><p>Other components of building a great team and technology driven culture include:</p><ul><li>Always pay attention to performance. Digging deep on gnarly performance issues often leads you and the team to learn many hidden corners of your system, subsystems, and dependent services.</li><li>Build the culture of “Ship &amp; Re-factor continually”. As a manager, if you feel like you don’t have the bandwidth to take on an item in a critical path, dive into a re-factoring project/task that doesn’t have the same time pressures.</li><li>Write unit tests. Celebrate great unit tests! Unit tests are a great way to stay in touch with not only the code, but also the core workflow!</li><li>Fostering the open source culture (both “internal open source” and true open source). This is easier said than done as time pressures lead us to taking shortcuts that keep libraries, utilities, tools, and SDKs in the same git repo as our product.</li><li>Instituting a healthy Pull Request review process that works for your team.</li><li>Be the champion of Developer Productivity: Happy Developers build great products. A developer will join a team where a build takes 30 seconds over a team where a build takes ½ hr over a team where someone from SCM has to kick off a build. I subscribe to the philosophy that you’ve gotta be able to run a scaled-down production system on your laptop.</li><li>Push for Continuous Delivery/Continous Operations. Immerse yourself in CD/CO and help your team get to NO-DRAMA releases.</li><li>Argue &amp; fight about Database design, be more forgiving on higher layers. Mistakes at the foundational layers can cost dearly, but you should encourage your Engineers to take more risks as they go up the stack.</li></ul><p></p>]]></content:encoded></item></channel></rss>