From 69da7ad231c560180833ec5c87a81ce78aabade0 Mon Sep 17 00:00:00 2001 From: Kalyanasundaram Somasundaram Date: Thu, 5 Nov 2020 19:15:36 +0550 Subject: [PATCH] Deployed e2485b0 with MkDocs version: 1.0.4 --- 404.html | 29 ++ big_data/architecture/index.html | 345 ++++++++++++++++++++++ big_data/evolution/index.html | 259 ++++++++++++++++ big_data/images/hadoop_evolution.png | Bin 0 -> 148008 bytes big_data/images/hdfs_architecture.png | Bin 0 -> 21159 bytes big_data/images/map_reduce.jpg | Bin 0 -> 33395 bytes big_data/images/mapreduce_example.jpg | Bin 0 -> 30669 bytes big_data/images/pig_example.png | Bin 0 -> 379489 bytes big_data/images/yarn_architecture.gif | Bin 0 -> 30978 bytes big_data/intro/index.html | 295 ++++++++++++++++++ big_data/overview/index.html | 275 +++++++++++++++++ big_data/tasks/index.html | 278 +++++++++++++++++ big_data/usage/index.html | 272 +++++++++++++++++ git/branches/index.html | 29 ++ git/git-basics/index.html | 29 ++ git/github-hooks/index.html | 29 ++ index.html | 31 +- python_web/intro/index.html | 29 ++ python_web/python-concepts/index.html | 29 ++ python_web/python-web-flask/index.html | 29 ++ python_web/sre-conclusion/index.html | 29 ++ python_web/url-shorten-app/index.html | 29 ++ search/search_index.json | 2 +- sitemap.xml | 58 +++- sitemap.xml.gz | Bin 204 -> 208 bytes systems_design/availability/index.html | 29 ++ systems_design/conclusion/index.html | 33 ++- systems_design/fault-tolerance/index.html | 29 ++ systems_design/intro/index.html | 29 ++ systems_design/scalability/index.html | 29 ++ 30 files changed, 2207 insertions(+), 18 deletions(-) create mode 100644 big_data/architecture/index.html create mode 100644 big_data/evolution/index.html create mode 100644 big_data/images/hadoop_evolution.png create mode 100644 big_data/images/hdfs_architecture.png create mode 100644 big_data/images/map_reduce.jpg create mode 100644 big_data/images/mapreduce_example.jpg create mode 100644 big_data/images/pig_example.png create mode 100644 big_data/images/yarn_architecture.gif create mode 100644 big_data/intro/index.html create mode 100644 big_data/overview/index.html create mode 100644 big_data/tasks/index.html create mode 100644 big_data/usage/index.html diff --git a/404.html b/404.html index 098e0e0..779cb01 100644 --- a/404.html +++ b/404.html @@ -113,6 +113,35 @@
  • Conclusion +
  • + + + diff --git a/big_data/architecture/index.html b/big_data/architecture/index.html new file mode 100644 index 0000000..346e3e8 --- /dev/null +++ b/big_data/architecture/index.html @@ -0,0 +1,345 @@ + + + + + + + + + + + Architecture of Hadoop - school_of_sre + + + + + + + + + + + + + + + + + +
    + +
    + +

    Architecture of Hadoop

    +
      +
    1. +

      HDFS

      +
        +
      1. The Hadoop Distributed File System (HDFS) is a distributed file system designed to run on commodity hardware. It has many similarities with existing distributed file systems. However, the differences from other distributed file systems are significant.
      2. +
      3. HDFS is highly fault-tolerant and is designed to be deployed on low-cost hardware. HDFS provides high throughput access to application data and is suitable for applications that have large data sets.
      4. +
      5. HDFS is part of the Apache Hadoop Core project.
      6. +
      +

      HDFS Architecture

      +
      1. NameNode: is the arbitrator and central repository of file namespace in the cluster. The NameNode executes the operations such as opening, closing, and renaming files and directories.
      +2. DataNode: manages the storage attached to the node on which it runs. It is responsible for serving all the read and write requests. It performs operations on instructions on NameNode such as creation, deletion, and replications of blocks.
      +3. Client: Responsible for getting the required metadata from the namenode and then communicating with the datanodes for reads and writes.
      +
      +
    2. +
    3. +

      YARN

      +
        +
      1. YARN stands for “Yet Another Resource Negotiator“. It was introduced in Hadoop 2.0 to remove the bottleneck on Job Tracker which was present in Hadoop 1.0. YARN was described as a “Redesigned Resource Manager” at the time of its launching, but it has now evolved to be known as a large-scale distributed operating system used for Big Data processing.
      2. +
      3. The main components of YARN architecture include:
      4. +
      +

      YARN Architecture

      +
      1. Client: It submits map-reduce jobs to the resource manager.
      +2. Resource Manager: It is the master daemon of YARN and is responsible for resource assignment and management among all the applications. Whenever it receives a processing request, it forwards it to the corresponding node manager and allocates resources for the completion of the request accordingly. It has two major components:
      +3. Scheduler: It performs scheduling based on the allocated application and available resources. It is a pure scheduler, which means that it does not perform other tasks such as monitoring or tracking and does not guarantee a restart if a task fails. The YARN scheduler supports plugins such as Capacity Scheduler and Fair Scheduler to partition the cluster resources.
      +4. Application manager: It is responsible for accepting the application and negotiating the first container from the resource manager. It also restarts the Application Manager container if a task fails.
      +5. Node Manager: It takes care of individual nodes on the Hadoop cluster and manages application and workflow and that particular node. Its primary job is to keep-up with the Node Manager. It monitors resource usage, performs log management and also kills a container based on directions from the resource manager. It is also responsible for creating the container process and starting it on the request of the Application master.
      +6. Application Master: An application is a single job submitted to a framework. The application manager is responsible for negotiating resources with the resource manager, tracking the status and monitoring progress of a single application. The application master requests the container from the node manager by sending a Container Launch Context(CLC) which includes everything an application needs to run. Once the application is started, it sends the health report to the resource manager from time-to-time.
      +7. Container: It is a collection of physical resources such as RAM, CPU cores and disk on a single node. The containers are invoked by Container Launch Context(CLC) which is a record that contains information such as environment variables, security tokens, dependencies etc.
      +
      +
    4. +
    +

    MapReduce framework

    +

    MapReduce Framework

    +
    1. The term MapReduce represents two separate and distinct tasks Hadoop programs perform-Map Job and Reduce Job. Map jobs take data sets as input and process them to produce key value pairs. Reduce job takes the output of the Map job i.e. the key value pairs and aggregates them to produce desired results. 
    +2. Hadoop MapReduce (Hadoop Map/Reduce) is a software framework for distributed processing of large data sets on computing clusters. Mapreduce helps to  split the input data set into a number of parts and run a program on all data parts parallel at once.
    +3. Please find the below Word count example demonstrating the usage of MapReduce framework:
    +
    +

    Word Count Example

    +

    Other tooling around hadoop

    +
      +
    1. Hive
        +
      1. Uses a language called HQL which is very SQL like. Gives non-programmers the ability to query and analyze data in Hadoop. Is basically an abstraction layer on top of map-reduce.
      2. +
      3. Ex. HQL query:
          +
        1. SELECT pet.name, comment FROM pet JOIN event ON (pet.name = event.name);
        2. +
        +
      4. +
      5. In mysql:
          +
        1. SELECT pet.name, comment FROM pet, event WHERE pet.name = event.name;
        2. +
        +
      6. +
      +
    2. +
    3. +

      Pig

      +
        +
      1. Uses a scripting language called Pig Latin, which is more workflow driven. Don't need to be an expert Java programmer but need a few coding skills. Is also an abstraction layer on top of map-reduce.
      2. +
      3. Here is a quick question for you: +What is the output of running the pig queries in the right column against the data present in the left column in the below image?
      4. +
      +

      Pig Example

      +

      Output: +mysql +7,Komal,Nayak,24,9848022334,trivendram +8,Bharathi,Nambiayar,24,9848022333,Chennai +5,Trupthi,Mohanthy,23,9848022336,Bhuwaneshwar +6,Archana,Mishra,23,9848022335,Chennai +3. Spark +1. Spark provides primitives for in-memory cluster computing that allows user programs to load data into a cluster’s memory and query it repeatedly, making it well suited to machine learning algorithms. +4. Presto +1. Presto is a high performance, distributed SQL query engine for Big Data. +2. Its architecture allows users to query a variety of data sources such as Hadoop, AWS S3, Alluxio, MySQL, Cassandra, Kafka, and MongoDB. +3. Example presto query: + mysql + use studentDB; + show tables; + SELECT roll_no, name FROM studentDB.studentDetails where section=’A’ limit 5;

      +
    4. +
    +

    Data Serialisation and storage

    +
      +
    1. In order to transport the data over the network or to store on some persistent storage, we use the process of translating data structures or objects state into binary or textual form. We call this process serialization..
    2. +
    3. Avro data is stored in a container file (a .avro file) and its schema (the .avsc file) is stored with the data file.
    4. +
    5. Apache Hive provides support to store a table as Avro and can also query data in this serialisation format.
    6. +
    +
    + + + + + + + + + + diff --git a/big_data/evolution/index.html b/big_data/evolution/index.html new file mode 100644 index 0000000..015b09d --- /dev/null +++ b/big_data/evolution/index.html @@ -0,0 +1,259 @@ + + + + + + + + + + + Evolution of Hadoop - school_of_sre + + + + + + + + + + + + + + + + + +
    + +
    + +

    Evolution of Hadoop

    +

    Evolution of hadoop

    +
    + + + + + + + + + + diff --git a/big_data/images/hadoop_evolution.png b/big_data/images/hadoop_evolution.png new file mode 100644 index 0000000000000000000000000000000000000000..71e1e26d0a004f60e29918704c79fd5625dd60b1 GIT binary patch literal 148008 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%0A;2ux=7&mjo100&*#yh7Th9_X@1%pF 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

    ^6rAo)Ds5Mv!?o(}7=Kw>hZXxf)>sOW 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

    + + diff --git a/git/git-basics/index.html b/git/git-basics/index.html index 9ae10e5..2001bbe 100644 --- a/git/git-basics/index.html +++ b/git/git-basics/index.html @@ -113,6 +113,35 @@
  • Conclusion +
  • + + + diff --git a/git/github-hooks/index.html b/git/github-hooks/index.html index 13c6e36..e0b5104 100644 --- a/git/github-hooks/index.html +++ b/git/github-hooks/index.html @@ -113,6 +113,35 @@
  • Conclusion +
  • + + + diff --git a/index.html b/index.html index 4db5a2b..eea8ca3 100644 --- a/index.html +++ b/index.html @@ -113,6 +113,35 @@
  • Conclusion +
  • + + + @@ -229,5 +258,5 @@ diff --git a/python_web/intro/index.html b/python_web/intro/index.html index 40027f9..cae8e6f 100644 --- a/python_web/intro/index.html +++ b/python_web/intro/index.html @@ -113,6 +113,35 @@
  • Conclusion +
  • + + + diff --git a/python_web/python-concepts/index.html b/python_web/python-concepts/index.html index 844e566..f37b4f5 100644 --- a/python_web/python-concepts/index.html +++ b/python_web/python-concepts/index.html @@ -113,6 +113,35 @@
  • Conclusion +
  • + + + diff --git a/python_web/python-web-flask/index.html b/python_web/python-web-flask/index.html index 4c1278a..181e946 100644 --- a/python_web/python-web-flask/index.html +++ b/python_web/python-web-flask/index.html @@ -113,6 +113,35 @@
  • Conclusion +
  • + + + diff --git a/python_web/sre-conclusion/index.html b/python_web/sre-conclusion/index.html index 3260933..ae97b55 100644 --- a/python_web/sre-conclusion/index.html +++ b/python_web/sre-conclusion/index.html @@ -113,6 +113,35 @@
  • Conclusion +
  • + + + diff --git a/python_web/url-shorten-app/index.html b/python_web/url-shorten-app/index.html index 1bec57e..5de04d3 100644 --- a/python_web/url-shorten-app/index.html +++ b/python_web/url-shorten-app/index.html @@ -113,6 +113,35 @@
  • Conclusion +
  • + + + diff --git a/search/search_index.json b/search/search_index.json index c59e0b0..1cb17ce 100644 --- a/search/search_index.json +++ b/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["en"],"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"Hello, World!!!","title":"Home"},{"location":"git/branches/","text":"Working With Branches Coming back to our local repo which has two commits. So far, what we have is a single line of history. Commits are chained in a single line. But sometimes you may have a need to work on two different features in parallel in the same repo. Now one option here could be making a new folder/repo with the same code and use that for another feature development. But there's a better way. Use branches. Since git follows tree like structure for commits, we can use branches to work on different sets of features. From a commit, two or more branches can be created and branches can also be merged. Using branches, there can exist multiple lines of histories and we can checkout to any of them and work on it. Checking out, as we discussed earlier, would simply mean replacing contents of the directory (repo) with contents snapshot at the checked out version. Let's create a branch and see how it looks like: spatel1-mn1:school-of-sre spatel1$ git branch b1 spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph * 7f3b00e (HEAD - master, b1) adding file 2 * df2fb7a adding file 1 We create a branch called b1 . Git log tells us that b1 also points to the last commit (7f3b00e) but the HEAD is still pointing to master. If you remember, HEAD points to the commit/reference wherever you are checkout to. So if we checkout to b1 , HEAD should point to that. Let's confirm: spatel1-mn1:school-of-sre spatel1$ git checkout b1 Switched to branch 'b1' spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph * 7f3b00e (HEAD - b1, master) adding file 2 * df2fb7a adding file 1 b1 still points to the same commit but HEAD now points to b1 . Since we create a branch at commit 7f3b00e , there will be two lines of histories starting this commit. Depending on which branch you are checked out on, the line of history will progress. At this moment, we are checked out on branch b1 , so making a new commit will advance branch reference b1 to that commit and current b1 commit will become its parent. Let's do that. # Creating a file and making a commit spatel1-mn1:school-of-sre spatel1$ echo I am a file in b1 branch b1.txt spatel1-mn1:school-of-sre spatel1$ git add b1.txt spatel1-mn1:school-of-sre spatel1$ git commit -m adding b1 file [b1 872a38f] adding b1 file 1 file changed, 1 insertion(+) create mode 100644 b1.txt # The new line of history spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph * 872a38f (HEAD - b1) adding b1 file * 7f3b00e (master) adding file 2 * df2fb7a adding file 1 spatel1-mn1:school-of-sre spatel1$ Do note that master is still pointing to the old commit it was pointing to. We can now checkout to master branch and make commits there. This will result in another line of history starting from commit 7f3b00e. # checkout to master branch spatel1-mn1:school-of-sre spatel1$ git checkout master Switched to branch 'master' # Creating a new commit on master branch spatel1-mn1:school-of-sre spatel1$ echo new file in master branch master.txt spatel1-mn1:school-of-sre spatel1$ git add master.txt spatel1-mn1:school-of-sre spatel1$ git commit -m adding master.txt file [master 60dc441] adding master.txt file 1 file changed, 1 insertion(+) create mode 100644 master.txt # The history line spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph * 60dc441 (HEAD - master) adding master.txt file * 7f3b00e adding file 2 * df2fb7a adding file 1 Notice how branch b1 is not visible here since we are checkout on master. Let's try to visualize both to get the whole picture: spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph --all * 60dc441 (HEAD - master) adding master.txt file | * 872a38f (b1) adding b1 file |/ * 7f3b00e adding file 2 * df2fb7a adding file 1 Above tree structure should make things clear. Notice a clear branch/fork on commit 7f3b00e. This is how we create branches. Now they both are two separate lines of history on which feature development can be done independently. To reiterate, internally, git is just a tree of commits. Branch names (human readable) are pointers to those commits in the tree. We use various git commands to work with the tree structure and references. Git accordingly modifies contents of our repo. Merges Now say the feature you were working on branch b1 is complete. And you need to merge it on master branch, where all the final version of code goes. So first you will checkout to branch master and then you will pull the latest code from upstream (eg: GitHub). Then you need to merge your code from b1 into master. And there could be two ways this can be done. Here is the current history: spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph --all * 60dc441 (HEAD - master) adding master.txt file | * 872a38f (b1) adding b1 file |/ * 7f3b00e adding file 2 * df2fb7a adding file 1 Option 1: Directly merge the branch. Merging the branch b1 into master will result in a new merge commit which will merge changes from two different lines of history and create a new commit of the result. spatel1-mn1:school-of-sre spatel1$ git merge b1 Merge made by the 'recursive' strategy. b1.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 b1.txt spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph --all * 8fc28f9 (HEAD - master) Merge branch 'b1' |\\ | * 872a38f (b1) adding b1 file * | 60dc441 adding master.txt file |/ * 7f3b00e adding file 2 * df2fb7a adding file 1 You can see a new merge commit created (8fc28f9). You will be prompted for the commit message. If there are a lot of branches in the repo, this result will end-up with a lot of merge commits. Which looks ugly compared to a single line of history of development. So let's look at an alternative approach First let's reset our last merge and go to the previous state. spatel1-mn1:school-of-sre spatel1$ git reset --hard 60dc441 HEAD is now at 60dc441 adding master.txt file spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph --all * 60dc441 (HEAD - master) adding master.txt file | * 872a38f (b1) adding b1 file |/ * 7f3b00e adding file 2 * df2fb7a adding file 1 Option 2: Rebase. Now, instead of merging two branches which has a similar base (commit: 7f3b00e), let us rebase branch b1 on to current master. What this means is take branch b1 (from commit 7f3b00e to commit 872a38f) and rebase (put them on top of) master (60dc441). # Switch to b1 spatel1-mn1:school-of-sre spatel1$ git checkout b1 Switched to branch 'b1' # Rebase (b1 which is current branch) on master spatel1-mn1:school-of-sre spatel1$ git rebase master First, rewinding head to replay your work on top of it... Applying: adding b1 file # The result spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph --all * 5372c8f (HEAD - b1) adding b1 file * 60dc441 (master) adding master.txt file * 7f3b00e adding file 2 * df2fb7a adding file 1 You can see b1 which had 1 commit. That commit's parent was 7f3b00e . But since we rebase it on master ( 60dc441 ). That becomes the parent now. As a side effect, you also see it has become a single line of history. Now if we were to merge b1 into master , it would simply mean change master to point to 5372c8f which is b1 . Let's try it: # checkout to master since we want to merge code into master spatel1-mn1:school-of-sre spatel1$ git checkout master Switched to branch 'master' # the current history, where b1 is based on master spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph --all * 5372c8f (b1) adding b1 file * 60dc441 (HEAD - master) adding master.txt file * 7f3b00e adding file 2 * df2fb7a adding file 1 # Performing the merge, notice the fast-forward message spatel1-mn1:school-of-sre spatel1$ git merge b1 Updating 60dc441..5372c8f Fast-forward b1.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 b1.txt # The Result spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph --all * 5372c8f (HEAD - master, b1) adding b1 file * 60dc441 adding master.txt file * 7f3b00e adding file 2 * df2fb7a adding file 1 Now you see both b1 and master are pointing to the same commit. Your code has been merged to the master branch and it can be pushed. Also we have clean line of history! :D","title":"Working With Branches"},{"location":"git/branches/#working-with-branches","text":"Coming back to our local repo which has two commits. So far, what we have is a single line of history. Commits are chained in a single line. But sometimes you may have a need to work on two different features in parallel in the same repo. Now one option here could be making a new folder/repo with the same code and use that for another feature development. But there's a better way. Use branches. Since git follows tree like structure for commits, we can use branches to work on different sets of features. From a commit, two or more branches can be created and branches can also be merged. Using branches, there can exist multiple lines of histories and we can checkout to any of them and work on it. Checking out, as we discussed earlier, would simply mean replacing contents of the directory (repo) with contents snapshot at the checked out version. Let's create a branch and see how it looks like: spatel1-mn1:school-of-sre spatel1$ git branch b1 spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph * 7f3b00e (HEAD - master, b1) adding file 2 * df2fb7a adding file 1 We create a branch called b1 . Git log tells us that b1 also points to the last commit (7f3b00e) but the HEAD is still pointing to master. If you remember, HEAD points to the commit/reference wherever you are checkout to. So if we checkout to b1 , HEAD should point to that. Let's confirm: spatel1-mn1:school-of-sre spatel1$ git checkout b1 Switched to branch 'b1' spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph * 7f3b00e (HEAD - b1, master) adding file 2 * df2fb7a adding file 1 b1 still points to the same commit but HEAD now points to b1 . Since we create a branch at commit 7f3b00e , there will be two lines of histories starting this commit. Depending on which branch you are checked out on, the line of history will progress. At this moment, we are checked out on branch b1 , so making a new commit will advance branch reference b1 to that commit and current b1 commit will become its parent. Let's do that. # Creating a file and making a commit spatel1-mn1:school-of-sre spatel1$ echo I am a file in b1 branch b1.txt spatel1-mn1:school-of-sre spatel1$ git add b1.txt spatel1-mn1:school-of-sre spatel1$ git commit -m adding b1 file [b1 872a38f] adding b1 file 1 file changed, 1 insertion(+) create mode 100644 b1.txt # The new line of history spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph * 872a38f (HEAD - b1) adding b1 file * 7f3b00e (master) adding file 2 * df2fb7a adding file 1 spatel1-mn1:school-of-sre spatel1$ Do note that master is still pointing to the old commit it was pointing to. We can now checkout to master branch and make commits there. This will result in another line of history starting from commit 7f3b00e. # checkout to master branch spatel1-mn1:school-of-sre spatel1$ git checkout master Switched to branch 'master' # Creating a new commit on master branch spatel1-mn1:school-of-sre spatel1$ echo new file in master branch master.txt spatel1-mn1:school-of-sre spatel1$ git add master.txt spatel1-mn1:school-of-sre spatel1$ git commit -m adding master.txt file [master 60dc441] adding master.txt file 1 file changed, 1 insertion(+) create mode 100644 master.txt # The history line spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph * 60dc441 (HEAD - master) adding master.txt file * 7f3b00e adding file 2 * df2fb7a adding file 1 Notice how branch b1 is not visible here since we are checkout on master. Let's try to visualize both to get the whole picture: spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph --all * 60dc441 (HEAD - master) adding master.txt file | * 872a38f (b1) adding b1 file |/ * 7f3b00e adding file 2 * df2fb7a adding file 1 Above tree structure should make things clear. Notice a clear branch/fork on commit 7f3b00e. This is how we create branches. Now they both are two separate lines of history on which feature development can be done independently. To reiterate, internally, git is just a tree of commits. Branch names (human readable) are pointers to those commits in the tree. We use various git commands to work with the tree structure and references. Git accordingly modifies contents of our repo.","title":"Working With Branches"},{"location":"git/branches/#merges","text":"Now say the feature you were working on branch b1 is complete. And you need to merge it on master branch, where all the final version of code goes. So first you will checkout to branch master and then you will pull the latest code from upstream (eg: GitHub). Then you need to merge your code from b1 into master. And there could be two ways this can be done. Here is the current history: spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph --all * 60dc441 (HEAD - master) adding master.txt file | * 872a38f (b1) adding b1 file |/ * 7f3b00e adding file 2 * df2fb7a adding file 1 Option 1: Directly merge the branch. Merging the branch b1 into master will result in a new merge commit which will merge changes from two different lines of history and create a new commit of the result. spatel1-mn1:school-of-sre spatel1$ git merge b1 Merge made by the 'recursive' strategy. b1.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 b1.txt spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph --all * 8fc28f9 (HEAD - master) Merge branch 'b1' |\\ | * 872a38f (b1) adding b1 file * | 60dc441 adding master.txt file |/ * 7f3b00e adding file 2 * df2fb7a adding file 1 You can see a new merge commit created (8fc28f9). You will be prompted for the commit message. If there are a lot of branches in the repo, this result will end-up with a lot of merge commits. Which looks ugly compared to a single line of history of development. So let's look at an alternative approach First let's reset our last merge and go to the previous state. spatel1-mn1:school-of-sre spatel1$ git reset --hard 60dc441 HEAD is now at 60dc441 adding master.txt file spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph --all * 60dc441 (HEAD - master) adding master.txt file | * 872a38f (b1) adding b1 file |/ * 7f3b00e adding file 2 * df2fb7a adding file 1 Option 2: Rebase. Now, instead of merging two branches which has a similar base (commit: 7f3b00e), let us rebase branch b1 on to current master. What this means is take branch b1 (from commit 7f3b00e to commit 872a38f) and rebase (put them on top of) master (60dc441). # Switch to b1 spatel1-mn1:school-of-sre spatel1$ git checkout b1 Switched to branch 'b1' # Rebase (b1 which is current branch) on master spatel1-mn1:school-of-sre spatel1$ git rebase master First, rewinding head to replay your work on top of it... Applying: adding b1 file # The result spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph --all * 5372c8f (HEAD - b1) adding b1 file * 60dc441 (master) adding master.txt file * 7f3b00e adding file 2 * df2fb7a adding file 1 You can see b1 which had 1 commit. That commit's parent was 7f3b00e . But since we rebase it on master ( 60dc441 ). That becomes the parent now. As a side effect, you also see it has become a single line of history. Now if we were to merge b1 into master , it would simply mean change master to point to 5372c8f which is b1 . Let's try it: # checkout to master since we want to merge code into master spatel1-mn1:school-of-sre spatel1$ git checkout master Switched to branch 'master' # the current history, where b1 is based on master spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph --all * 5372c8f (b1) adding b1 file * 60dc441 (HEAD - master) adding master.txt file * 7f3b00e adding file 2 * df2fb7a adding file 1 # Performing the merge, notice the fast-forward message spatel1-mn1:school-of-sre spatel1$ git merge b1 Updating 60dc441..5372c8f Fast-forward b1.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 b1.txt # The Result spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph --all * 5372c8f (HEAD - master, b1) adding b1 file * 60dc441 adding master.txt file * 7f3b00e adding file 2 * df2fb7a adding file 1 Now you see both b1 and master are pointing to the same commit. Your code has been merged to the master branch and it can be pushed. Also we have clean line of history! :D","title":"Merges"},{"location":"git/git-basics/","text":"School Of SRE: Git Pre - Reads Have Git installed https://git-scm.com/downloads Have taken any git high level tutorial or following LinkedIn learning courses https://www.linkedin.com/learning/git-essential-training-the-basics/ https://www.linkedin.com/learning/git-branches-merges-and-remotes/ The Official Git Docs What to expect from this training As an engineer in the field of computer science, having knowledge of version control tools becomes almost a requirement. While there are a lot of version control tools that exist today, Git perhaps is the most used one and this course we will be working with Git. While this course does not start with Git 101 and expects basic knowledge of git as a prerequisite, it will reintroduce the git concepts known by you with details covering what is happening under the hood as you execute various git commands. So that next time you run a git command, you will be able to press enter more confidently! What is not covered under this training Advanced usage and specifics of internal implementation details of Git. Training Content Table of Contents Git Basics Working with Branches Git with Github Hooks Git Basics Though you might be aware already, let's revisit why we need a version control system. As the project grows and multiple developers start working on it, an efficient method for collaboration is warranted. Git helps the team collaborate easily and also maintains history of the changes happened with the codebase. Creating a Git Repo Any folder can be converted into a git repository. After executing the following command, we will see a .git folder within the folder, which makes our folder a git repository. All the magic that git does, .git folder is the enabler for the same. # creating an empty folder and changing current dir to it spatel1-mn1:~ spatel1$ cd /tmp spatel1-mn1:tmp spatel1$ mkdir school-of-sre spatel1-mn1:tmp spatel1$ cd school-of-sre/ # initialize a git repo spatel1-mn1:school-of-sre spatel1$ git init Initialized empty Git repository in /private/tmp/school-of-sre/.git/ As the output says, an empty git repo has been initialized in our folder. Let's take a look at what is there. spatel1-mn1:school-of-sre spatel1$ ls .git/ HEAD config description hooks info objects refs There are a bunch of folders and files in the .git folder. As I said, all these enables git to do its magic. We will look into some of these folders and files. But for now, what we have is an empty git repository. Tracking a File Now as you might already know, let us create a new file in our repo (we will refer to the folder as repo now.) And see git status spatel1-mn1:school-of-sre spatel1$ echo I am file 1 file1.txt spatel1-mn1:school-of-sre spatel1$ git status On branch master No commits yet Untracked files: (use git add file ... to include in what will be committed) file1.txt nothing added to commit but untracked files present (use git add to track) The current git status says No commits yet and there is one untracked file. Since we just created the file, git is not tracking that file. We explicitly need to ask git to track files and folders. (also checkout gitignore ) And how we do that is via git add command as suggested in the above output. Then we go ahead and create a commit. spatel1-mn1:school-of-sre spatel1$ git add file1.txt spatel1-mn1:school-of-sre spatel1$ git status On branch master No commits yet Changes to be committed: (use git rm --cached file ... to unstage) new file: file1.txt spatel1-mn1:school-of-sre spatel1$ git commit -m adding file 1 [master (root-commit) df2fb7a] adding file 1 1 file changed, 1 insertion(+) create mode 100644 file1.txt Notice how after adding the file, git status says Changes to be commited: . What it means is whatever is listed there, will be included in the next commit. Then we go ahead and create a commit, with an attached messaged via -m . More About a Commit Commit is a snapshot of the repo. Whenever a commit is made, a snapshot of the current state of repo (the folder) is taken and saved. Each commit has a unique ID. ( df2fb7a for the commit we made in the previous step). As we keep adding/changing more and more contents and keep making commits, all those snapshots are stored by git. Again, all this magic happens inside the .git folder. This is where all this snapshot or versions are stored. In an efficient manner. Adding More Changes Let us create one more file and commit the change. It would look the same as the previous commit we made. spatel1-mn1:school-of-sre spatel1$ echo I am file 2 file2.txt spatel1-mn1:school-of-sre spatel1$ git add file2.txt spatel1-mn1:school-of-sre spatel1$ git commit -m adding file 2 [master 7f3b00e] adding file 2 1 file changed, 1 insertion(+) create mode 100644 file2.txt A new commit with ID 7f3b00e has been created. You can issue git status at any time to see the state of the repository. **IMPORTANT: Note that commit IDs are long string (SHA) but we can refer to a commit by its initial few (8 or more) characters too. We will interchangeably using shorter and longer commit IDs.** Now that we have two commits, let's visualize them: spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph * 7f3b00e (HEAD - master) adding file 2 * df2fb7a adding file 1 git log , as the name suggests, prints the log of all the git commits. Here you see two additional arguments, --oneline prints the shorter version of the log, ie: the commit message only and not the person who made the commit and when. --graph prints it in graph format. Now at this moment the commits might look like just one in each line but all commits are stored as a tree like data structure internally by git. That means there can be two or more children commits of a given commit. And not just a single line of commits. We will look more into this part when we get to the Branches section. For now this is our commit history: df2fb7a === 7f3b00e Are commits really linked? As I just said, the two commits we just made are linked via tree like data structure and we saw how they are linked. But let's actually verify it. Everything in git is an object. Newly created files are stored as an object. Changes to file are stored as an objects and even commits are objects. To view contents of an object we can use the following command with the object's ID. We will take a look at content of the contents of the second commit spatel1-mn1:school-of-sre spatel1$ git cat-file -p 7f3b00e tree ebf3af44d253e5328340026e45a9fa9ae3ea1982 parent df2fb7a61f5d40c1191e0fdeb0fc5d6e7969685a author Sanket Patel spatel1@linkedin.com 1603273316 -0700 committer Sanket Patel spatel1@linkedin.com 1603273316 -0700 adding file 2 Take a note of parent attribute in the above output. It points to the commit id of the first commit we made. So this proves that they are linked! Additionally you can see the second commit's message in this object. As I said all this magic is enabled by .git folder and the object to which we are looking at also is in that folder. spatel1-mn1:school-of-sre spatel1$ ls .git/objects/7f/3b00eaa957815884198e2fdfec29361108d6a9 .git/objects/7f/3b00eaa957815884198e2fdfec29361108d6a9 It is stored in .git/objects/ folder. All the files and changes to them as well are stored in this folder. The Version Control part of Git We already can see two commits (versions) in our git log. One thing a version control tool gives you is ability to browse back and forth in history. For example: some of your users are running an old version of code and they are reporting an issue. In order to debug the issue, you need access to the old code. The one in your current repo is the latest code. In this example, you are working on the second commit (7f3b00e) and someone reported an issue with the code snapshot at commit (df2fb7a). This is how you would get access to the code at any older commit # Current contents, two files present patel1-mn1:school-of-sre spatel1$ ls file1.txt file2.txt # checking out to (an older) commit spatel1-mn1:school-of-sre spatel1$ git checkout df2fb7a Note: checking out 'df2fb7a'. You are in 'detached HEAD' state. You can look around, make experimental changes and commit them, and you can discard any commits you make in this state without impacting any branches by performing another checkout. If you want to create a new branch to retain commits you create, you may do so (now or later) by using -b with the checkout command again. Example: git checkout -b new-branch-name HEAD is now at df2fb7a adding file 1 # checking contents, can verify it has old contents spatel1-mn1:school-of-sre spatel1$ ls file1.txt So this is how we would get access to old versions/snapshots. All we need is a reference to that snapshot. Upon executing git checkout ... , what git does for you is use the .git folder, see what was the state of things (files and folders) at that version/reference and replace the contents of current directory with those contents. The then-existing content will no longer be present in the local dir (repo) but we can and will still get access to them because they are tracked via git commit and .git folder has them stored/tracked. Reference I mention in the previous section that we need a reference to the version. By default, git repo is made of tree of commits. And each commit has a unique IDs. But the unique ID is not the only thing we can reference commits via. There are multiple ways to reference commits. For example: HEAD is a reference to current commit. Whatever commit your repo is checked out at, HEAD will point to that. HEAD~1 is reference to previous commit. So while checking out previous version in section above, we could have done git checkout HEAD~1 . Similarly, master is also a reference (to a branch). Since git uses tree like structure to store commits, there of course will be branches. And the default branch is called master . Master (or any branch reference) will point to the latest commit in the branch. Even though we have checked out to the previous commit in out repo, master still points to the latest commit. And we can get back to the latest version by checkout at master reference spatel1-mn1:school-of-sre spatel1$ git checkout master Previous HEAD position was df2fb7a adding file 1 Switched to branch 'master' # now we will see latest code, with two files spatel1-mn1:school-of-sre spatel1$ ls file1.txt file2.txt Note, instead of master in above command, we could have used commit's ID as well. References and The Magic Let's look at the state of things. Two commits, master and HEAD references are pointing to the latest commit spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph * 7f3b00e (HEAD - master) adding file 2 * df2fb7a adding file 1 The magic? Let's examine these files: spatel1-mn1:school-of-sre spatel1$ cat .git/refs/heads/master 7f3b00eaa957815884198e2fdfec29361108d6a9 Viola! Where master is pointing to is stored in a file. Whenever git needs to know where master reference is pointing to, or if git needs to update where master points, it just needs to update the file above. So when you create a new commit, a new commit is created on top of the current commit and the master file is updated with the new commit's ID. Similary, for HEAD reference: spatel1-mn1:school-of-sre spatel1$ cat .git/HEAD ref: refs/heads/master We can see HEAD is pointing to a reference called refs/heads/master . So HEAD will point where ever the master points. Little Adventure We discussed how git will update the files as we execute commands. But let's try to do it ourselves, by hand, and see what happens. spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph * 7f3b00e (HEAD - master) adding file 2 * df2fb7a adding file 1 Now let's change master to point to the previous/first commit. spatel1-mn1:school-of-sre spatel1$ echo df2fb7a61f5d40c1191e0fdeb0fc5d6e7969685a .git/refs/heads/master spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph * df2fb7a (HEAD - master) adding file 1 # RESETTING TO ORIGINAL spatel1-mn1:school-of-sre spatel1$ echo 7f3b00eaa957815884198e2fdfec29361108d6a9 .git/refs/heads/master spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph * 7f3b00e (HEAD - master) adding file 2 * df2fb7a adding file 1 We just edited the master reference file and now we can see only the first commit in git log. Undoing the change to the file brings the state back to original. Not so much of magic, isn't it?","title":"Git Basics"},{"location":"git/git-basics/#school-of-sre-git","text":"","title":"School Of SRE: Git"},{"location":"git/git-basics/#pre-reads","text":"Have Git installed https://git-scm.com/downloads Have taken any git high level tutorial or following LinkedIn learning courses https://www.linkedin.com/learning/git-essential-training-the-basics/ https://www.linkedin.com/learning/git-branches-merges-and-remotes/ The Official Git Docs","title":"Pre - Reads"},{"location":"git/git-basics/#what-to-expect-from-this-training","text":"As an engineer in the field of computer science, having knowledge of version control tools becomes almost a requirement. While there are a lot of version control tools that exist today, Git perhaps is the most used one and this course we will be working with Git. While this course does not start with Git 101 and expects basic knowledge of git as a prerequisite, it will reintroduce the git concepts known by you with details covering what is happening under the hood as you execute various git commands. So that next time you run a git command, you will be able to press enter more confidently!","title":"What to expect from this training"},{"location":"git/git-basics/#what-is-not-covered-under-this-training","text":"Advanced usage and specifics of internal implementation details of Git.","title":"What is not covered under this training"},{"location":"git/git-basics/#training-content","text":"","title":"Training Content"},{"location":"git/git-basics/#table-of-contents","text":"Git Basics Working with Branches Git with Github Hooks","title":"Table of Contents"},{"location":"git/git-basics/#git-basics","text":"Though you might be aware already, let's revisit why we need a version control system. As the project grows and multiple developers start working on it, an efficient method for collaboration is warranted. Git helps the team collaborate easily and also maintains history of the changes happened with the codebase.","title":"Git Basics"},{"location":"git/git-basics/#creating-a-git-repo","text":"Any folder can be converted into a git repository. After executing the following command, we will see a .git folder within the folder, which makes our folder a git repository. All the magic that git does, .git folder is the enabler for the same. # creating an empty folder and changing current dir to it spatel1-mn1:~ spatel1$ cd /tmp spatel1-mn1:tmp spatel1$ mkdir school-of-sre spatel1-mn1:tmp spatel1$ cd school-of-sre/ # initialize a git repo spatel1-mn1:school-of-sre spatel1$ git init Initialized empty Git repository in /private/tmp/school-of-sre/.git/ As the output says, an empty git repo has been initialized in our folder. Let's take a look at what is there. spatel1-mn1:school-of-sre spatel1$ ls .git/ HEAD config description hooks info objects refs There are a bunch of folders and files in the .git folder. As I said, all these enables git to do its magic. We will look into some of these folders and files. But for now, what we have is an empty git repository.","title":"Creating a Git Repo"},{"location":"git/git-basics/#tracking-a-file","text":"Now as you might already know, let us create a new file in our repo (we will refer to the folder as repo now.) And see git status spatel1-mn1:school-of-sre spatel1$ echo I am file 1 file1.txt spatel1-mn1:school-of-sre spatel1$ git status On branch master No commits yet Untracked files: (use git add file ... to include in what will be committed) file1.txt nothing added to commit but untracked files present (use git add to track) The current git status says No commits yet and there is one untracked file. Since we just created the file, git is not tracking that file. We explicitly need to ask git to track files and folders. (also checkout gitignore ) And how we do that is via git add command as suggested in the above output. Then we go ahead and create a commit. spatel1-mn1:school-of-sre spatel1$ git add file1.txt spatel1-mn1:school-of-sre spatel1$ git status On branch master No commits yet Changes to be committed: (use git rm --cached file ... to unstage) new file: file1.txt spatel1-mn1:school-of-sre spatel1$ git commit -m adding file 1 [master (root-commit) df2fb7a] adding file 1 1 file changed, 1 insertion(+) create mode 100644 file1.txt Notice how after adding the file, git status says Changes to be commited: . What it means is whatever is listed there, will be included in the next commit. Then we go ahead and create a commit, with an attached messaged via -m .","title":"Tracking a File"},{"location":"git/git-basics/#more-about-a-commit","text":"Commit is a snapshot of the repo. Whenever a commit is made, a snapshot of the current state of repo (the folder) is taken and saved. Each commit has a unique ID. ( df2fb7a for the commit we made in the previous step). As we keep adding/changing more and more contents and keep making commits, all those snapshots are stored by git. Again, all this magic happens inside the .git folder. This is where all this snapshot or versions are stored. In an efficient manner.","title":"More About a Commit"},{"location":"git/git-basics/#adding-more-changes","text":"Let us create one more file and commit the change. It would look the same as the previous commit we made. spatel1-mn1:school-of-sre spatel1$ echo I am file 2 file2.txt spatel1-mn1:school-of-sre spatel1$ git add file2.txt spatel1-mn1:school-of-sre spatel1$ git commit -m adding file 2 [master 7f3b00e] adding file 2 1 file changed, 1 insertion(+) create mode 100644 file2.txt A new commit with ID 7f3b00e has been created. You can issue git status at any time to see the state of the repository. **IMPORTANT: Note that commit IDs are long string (SHA) but we can refer to a commit by its initial few (8 or more) characters too. We will interchangeably using shorter and longer commit IDs.** Now that we have two commits, let's visualize them: spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph * 7f3b00e (HEAD - master) adding file 2 * df2fb7a adding file 1 git log , as the name suggests, prints the log of all the git commits. Here you see two additional arguments, --oneline prints the shorter version of the log, ie: the commit message only and not the person who made the commit and when. --graph prints it in graph format. Now at this moment the commits might look like just one in each line but all commits are stored as a tree like data structure internally by git. That means there can be two or more children commits of a given commit. And not just a single line of commits. We will look more into this part when we get to the Branches section. For now this is our commit history: df2fb7a === 7f3b00e","title":"Adding More Changes"},{"location":"git/git-basics/#are-commits-really-linked","text":"As I just said, the two commits we just made are linked via tree like data structure and we saw how they are linked. But let's actually verify it. Everything in git is an object. Newly created files are stored as an object. Changes to file are stored as an objects and even commits are objects. To view contents of an object we can use the following command with the object's ID. We will take a look at content of the contents of the second commit spatel1-mn1:school-of-sre spatel1$ git cat-file -p 7f3b00e tree ebf3af44d253e5328340026e45a9fa9ae3ea1982 parent df2fb7a61f5d40c1191e0fdeb0fc5d6e7969685a author Sanket Patel spatel1@linkedin.com 1603273316 -0700 committer Sanket Patel spatel1@linkedin.com 1603273316 -0700 adding file 2 Take a note of parent attribute in the above output. It points to the commit id of the first commit we made. So this proves that they are linked! Additionally you can see the second commit's message in this object. As I said all this magic is enabled by .git folder and the object to which we are looking at also is in that folder. spatel1-mn1:school-of-sre spatel1$ ls .git/objects/7f/3b00eaa957815884198e2fdfec29361108d6a9 .git/objects/7f/3b00eaa957815884198e2fdfec29361108d6a9 It is stored in .git/objects/ folder. All the files and changes to them as well are stored in this folder.","title":"Are commits really linked?"},{"location":"git/git-basics/#the-version-control-part-of-git","text":"We already can see two commits (versions) in our git log. One thing a version control tool gives you is ability to browse back and forth in history. For example: some of your users are running an old version of code and they are reporting an issue. In order to debug the issue, you need access to the old code. The one in your current repo is the latest code. In this example, you are working on the second commit (7f3b00e) and someone reported an issue with the code snapshot at commit (df2fb7a). This is how you would get access to the code at any older commit # Current contents, two files present patel1-mn1:school-of-sre spatel1$ ls file1.txt file2.txt # checking out to (an older) commit spatel1-mn1:school-of-sre spatel1$ git checkout df2fb7a Note: checking out 'df2fb7a'. You are in 'detached HEAD' state. You can look around, make experimental changes and commit them, and you can discard any commits you make in this state without impacting any branches by performing another checkout. If you want to create a new branch to retain commits you create, you may do so (now or later) by using -b with the checkout command again. Example: git checkout -b new-branch-name HEAD is now at df2fb7a adding file 1 # checking contents, can verify it has old contents spatel1-mn1:school-of-sre spatel1$ ls file1.txt So this is how we would get access to old versions/snapshots. All we need is a reference to that snapshot. Upon executing git checkout ... , what git does for you is use the .git folder, see what was the state of things (files and folders) at that version/reference and replace the contents of current directory with those contents. The then-existing content will no longer be present in the local dir (repo) but we can and will still get access to them because they are tracked via git commit and .git folder has them stored/tracked.","title":"The Version Control part of Git"},{"location":"git/git-basics/#reference","text":"I mention in the previous section that we need a reference to the version. By default, git repo is made of tree of commits. And each commit has a unique IDs. But the unique ID is not the only thing we can reference commits via. There are multiple ways to reference commits. For example: HEAD is a reference to current commit. Whatever commit your repo is checked out at, HEAD will point to that. HEAD~1 is reference to previous commit. So while checking out previous version in section above, we could have done git checkout HEAD~1 . Similarly, master is also a reference (to a branch). Since git uses tree like structure to store commits, there of course will be branches. And the default branch is called master . Master (or any branch reference) will point to the latest commit in the branch. Even though we have checked out to the previous commit in out repo, master still points to the latest commit. And we can get back to the latest version by checkout at master reference spatel1-mn1:school-of-sre spatel1$ git checkout master Previous HEAD position was df2fb7a adding file 1 Switched to branch 'master' # now we will see latest code, with two files spatel1-mn1:school-of-sre spatel1$ ls file1.txt file2.txt Note, instead of master in above command, we could have used commit's ID as well.","title":"Reference"},{"location":"git/git-basics/#references-and-the-magic","text":"Let's look at the state of things. Two commits, master and HEAD references are pointing to the latest commit spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph * 7f3b00e (HEAD - master) adding file 2 * df2fb7a adding file 1 The magic? Let's examine these files: spatel1-mn1:school-of-sre spatel1$ cat .git/refs/heads/master 7f3b00eaa957815884198e2fdfec29361108d6a9 Viola! Where master is pointing to is stored in a file. Whenever git needs to know where master reference is pointing to, or if git needs to update where master points, it just needs to update the file above. So when you create a new commit, a new commit is created on top of the current commit and the master file is updated with the new commit's ID. Similary, for HEAD reference: spatel1-mn1:school-of-sre spatel1$ cat .git/HEAD ref: refs/heads/master We can see HEAD is pointing to a reference called refs/heads/master . So HEAD will point where ever the master points.","title":"References and The Magic"},{"location":"git/git-basics/#little-adventure","text":"We discussed how git will update the files as we execute commands. But let's try to do it ourselves, by hand, and see what happens. spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph * 7f3b00e (HEAD - master) adding file 2 * df2fb7a adding file 1 Now let's change master to point to the previous/first commit. spatel1-mn1:school-of-sre spatel1$ echo df2fb7a61f5d40c1191e0fdeb0fc5d6e7969685a .git/refs/heads/master spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph * df2fb7a (HEAD - master) adding file 1 # RESETTING TO ORIGINAL spatel1-mn1:school-of-sre spatel1$ echo 7f3b00eaa957815884198e2fdfec29361108d6a9 .git/refs/heads/master spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph * 7f3b00e (HEAD - master) adding file 2 * df2fb7a adding file 1 We just edited the master reference file and now we can see only the first commit in git log. Undoing the change to the file brings the state back to original. Not so much of magic, isn't it?","title":"Little Adventure"},{"location":"git/github-hooks/","text":"Git with Github Till now all the operations we did were in our local repo while git also helps us in a collaborative environment. GitHub is one place on the internet where you can centrally host your git repos and collaborate with other developers. Most of the workflow will remain the same as we discussed, with addition of couple of things: Pull: to pull latest changes from github (the central) repo Push: to push your changes to github repo so that it's available to all people GitHub has written nice guides and tutorials about this and you can refer them here: GitHub Hello World Git Handbook Hooks Git has another nice feature called hooks. Hooks are basically scripts which will be called when a certain event happens. Here is where hooks are located: spatel1-mn1:school-of-sre spatel1$ ls .git/hooks/ applypatch-msg.sample fsmonitor-watchman.sample pre-applypatch.sample pre-push.sample pre-receive.sample update.sample commit-msg.sample post-update.sample pre-commit.sample pre-rebase.sample prepare-commit-msg.sample Names are self explanatory. These hooks are useful when you want to do certain things when a certain event happens. Ie: if you want to run tests before pushing code, you would want to setup pre-push hooks. Let's try to create a pre commit hook. spatel1-mn1:school-of-sre spatel1$ echo echo this is from pre commit hook .git/hooks/pre-commit spatel1-mn1:school-of-sre spatel1$ chmod +x .git/hooks/pre-commit We basically create a file called pre-commit in hooks folder and make it executable. Now if we make a commit, we should see the message getting printed. spatel1-mn1:school-of-sre spatel1$ echo sample file sample.txt spatel1-mn1:school-of-sre spatel1$ git add sample.txt spatel1-mn1:school-of-sre spatel1$ git commit -m adding sample file this is from pre commit hook # ===== THE MESSAGE FROM HOOK EXECUTION [master 9894e05] adding sample file 1 file changed, 1 insertion(+) create mode 100644 sample.txt What next from here? There are a lot of git commands and features which we have not explored here. But with the base built-up, be sure to explore concepts like Cherrypick Squash Amend Stash Reset","title":"Github and Hooks"},{"location":"git/github-hooks/#git-with-github","text":"Till now all the operations we did were in our local repo while git also helps us in a collaborative environment. GitHub is one place on the internet where you can centrally host your git repos and collaborate with other developers. Most of the workflow will remain the same as we discussed, with addition of couple of things: Pull: to pull latest changes from github (the central) repo Push: to push your changes to github repo so that it's available to all people GitHub has written nice guides and tutorials about this and you can refer them here: GitHub Hello World Git Handbook","title":"Git with Github"},{"location":"git/github-hooks/#hooks","text":"Git has another nice feature called hooks. Hooks are basically scripts which will be called when a certain event happens. Here is where hooks are located: spatel1-mn1:school-of-sre spatel1$ ls .git/hooks/ applypatch-msg.sample fsmonitor-watchman.sample pre-applypatch.sample pre-push.sample pre-receive.sample update.sample commit-msg.sample post-update.sample pre-commit.sample pre-rebase.sample prepare-commit-msg.sample Names are self explanatory. These hooks are useful when you want to do certain things when a certain event happens. Ie: if you want to run tests before pushing code, you would want to setup pre-push hooks. Let's try to create a pre commit hook. spatel1-mn1:school-of-sre spatel1$ echo echo this is from pre commit hook .git/hooks/pre-commit spatel1-mn1:school-of-sre spatel1$ chmod +x .git/hooks/pre-commit We basically create a file called pre-commit in hooks folder and make it executable. Now if we make a commit, we should see the message getting printed. spatel1-mn1:school-of-sre spatel1$ echo sample file sample.txt spatel1-mn1:school-of-sre spatel1$ git add sample.txt spatel1-mn1:school-of-sre spatel1$ git commit -m adding sample file this is from pre commit hook # ===== THE MESSAGE FROM HOOK EXECUTION [master 9894e05] adding sample file 1 file changed, 1 insertion(+) create mode 100644 sample.txt","title":"Hooks"},{"location":"git/github-hooks/#what-next-from-here","text":"There are a lot of git commands and features which we have not explored here. But with the base built-up, be sure to explore concepts like Cherrypick Squash Amend Stash Reset","title":"What next from here?"},{"location":"python_web/intro/","text":"School of SRE: Python and The Web Pre - Reads Basic understanding of python language. Basic familiarity with flask framework. What to expect from this training This course is divided into two high level parts. In the first part, assuming familiarity with python language\u2019s basic operations and syntax usage, we will dive a little deeper into understanding python as a language. We will compare python with other programming languages that you might already know like Java and C. We will also explore concepts of Python objects and with help of that, explore python features like decorators. In the second part which will revolve around the web, and also assume familiarity with the Flask framework, we will start from the socket module and work with HTTP requests. This will demystify how frameworks like flask work internally. And to introduce SRE flavour to the course, we will design, develop and deploy (in theory) a URL shortening application. We will emphasize parts of the whole process that are more important as an SRE of the said app/service. What is not covered under this training Extensive knowledge of python internals and advanced python. Training Content Lab Environment Setup Have latest version of python installed TOC The Python Language Some Python Concepts Python Gotchas Python and Web Sockets Flask The URL Shortening App Design Scaling The App Monitoring The App The Python Language Assuming you know a little bit of C/C++ and Java, let's try to discuss the following questions in context of those two languages and python. You might have heard that C/C++ is a compiled language while python is an interpreted language. Generally, with compiled language we first compile the program and then run the executable while in case of python we run the source code directly like python hello_world.py . While Java, being an interpreted language, still has a separate compilation step and then its run. So what's really the difference? Compiled vs. Interpreted This might sound a little weird to you: python, in a way is a compiled language! Python has a compiler built-in! It is obvious in the case of java since we compile it using a separate command ie: javac helloWorld.java and it will produce a .class file which we know as a bytecode . Well, python is very similar to that. One difference here is that there is no separate compile command/binary needed to run a python program. What is the difference then, between java and python? Well, Java's compiler is more strict and sophisticated. As you might know Java is a statically typed language. So the compiler is written in a way that it can verify types related errors during compile time. While python being a dynamic language, types are not known until a program is run. So in a way, python compiler is dumb (or, less strict). But there indeed is a compile step involved when a python program is run. You might have seen python bytecode files with .pyc extension. Here is how you can see bytecode for a given python program. # Create a Hello World spatel1-mn1:tmp spatel1$ echo print('hello world') hello_world.py # Making sure it runs spatel1-mn1:tmp spatel1$ python3 hello_world.py hello world # The bytecode of the given program spatel1-mn1:tmp spatel1$ python -m dis hello_world.py 1 0 LOAD_NAME 0 (print) 2 LOAD_CONST 0 ('hello world') 4 CALL_FUNCTION 1 6 POP_TOP 8 LOAD_CONST 1 (None) 10 RETURN_VALUE Read more about dis module here Now coming to C/C++, there of course is a compiler. But the output is different than what java/python compiler would produce. Compiling a C program would produce what we also know as machine code . As opposed to bytecode. Running The Programs We know compilation is involved in all 3 languages we are discussing. Just that the compilers are different in nature and they output different types of content. In case of C/C++, the output is machine code which can be directly read by your operating system. When you execute that program, your OS will know how exactly to run it. But this is not the case with bytecode. Those bytecodes are language specific. Python has its own set of bytecode defined (more in dis module) and so does java. So naturally, your operating system will not know how to run it. To run this bytecode, we have something called Virtual Machines. Ie: The JVM or the Python VM (CPython, Jython). These so called Virtual Machines are the programs which can read the bytecode and run it on a given operating system. Python has multiple VMs available. Cpython is a python VM implemented in C language, similarly Jython is a Java implementation of python VM. At the end of the day, what they should be capable of is to understand python language syntax, be able to compile it to bytecode and be able to run that bytecode. You can implement a python VM in any language! (And people do so, just because it can be done) The Operating System +------------------------------------+ | | | | | | hello_world.py Python bytecode | Python VM Process | | | +----------------+ +----------------+ | +----------------+ | |print(... | COMPILE |LOAD_CONST... | | |Reads bytecode | | | +--------------- + +------------------- +line by line | | | | | | | |and executes. | | | | | | | | | | +----------------+ +----------------+ | +----------------+ | | | | | | | hello_world.c OS Specific machinecode | A New Process | | | +----------------+ +----------------+ | +----------------+ | |void main() { | COMPILE | binary contents| | | binary contents| | | +--------------- + +------------------- + | | | | | | | | | | | | | | | | | | +----------------+ +----------------+ | +----------------+ | | (binary contents | | runs as is) | | | | | +------------------------------------+ Two things to note for above diagram: Generally, when we run a python program, a python VM process is started which reads the python source code, compiles it to byte code and run it in a single step. Compiling is not a separate step. Shown only for illustration purpose. Binaries generated for C like languages are not exactly run as is. Since there are multiple types of binaries (eg: ELF), there are more complicated steps involved in order to run a binary but we will not go into that since all that is done at OS level.","title":"Intro"},{"location":"python_web/intro/#school-of-sre-python-and-the-web","text":"","title":"School of SRE: Python and The Web"},{"location":"python_web/intro/#pre-reads","text":"Basic understanding of python language. Basic familiarity with flask framework.","title":"Pre - Reads"},{"location":"python_web/intro/#what-to-expect-from-this-training","text":"This course is divided into two high level parts. In the first part, assuming familiarity with python language\u2019s basic operations and syntax usage, we will dive a little deeper into understanding python as a language. We will compare python with other programming languages that you might already know like Java and C. We will also explore concepts of Python objects and with help of that, explore python features like decorators. In the second part which will revolve around the web, and also assume familiarity with the Flask framework, we will start from the socket module and work with HTTP requests. This will demystify how frameworks like flask work internally. And to introduce SRE flavour to the course, we will design, develop and deploy (in theory) a URL shortening application. We will emphasize parts of the whole process that are more important as an SRE of the said app/service.","title":"What to expect from this training"},{"location":"python_web/intro/#what-is-not-covered-under-this-training","text":"Extensive knowledge of python internals and advanced python.","title":"What is not covered under this training"},{"location":"python_web/intro/#training-content","text":"","title":"Training Content"},{"location":"python_web/intro/#lab-environment-setup","text":"Have latest version of python installed","title":"Lab Environment Setup"},{"location":"python_web/intro/#toc","text":"The Python Language Some Python Concepts Python Gotchas Python and Web Sockets Flask The URL Shortening App Design Scaling The App Monitoring The App","title":"TOC"},{"location":"python_web/intro/#the-python-language","text":"Assuming you know a little bit of C/C++ and Java, let's try to discuss the following questions in context of those two languages and python. You might have heard that C/C++ is a compiled language while python is an interpreted language. Generally, with compiled language we first compile the program and then run the executable while in case of python we run the source code directly like python hello_world.py . While Java, being an interpreted language, still has a separate compilation step and then its run. So what's really the difference?","title":"The Python Language"},{"location":"python_web/intro/#compiled-vs-interpreted","text":"This might sound a little weird to you: python, in a way is a compiled language! Python has a compiler built-in! It is obvious in the case of java since we compile it using a separate command ie: javac helloWorld.java and it will produce a .class file which we know as a bytecode . Well, python is very similar to that. One difference here is that there is no separate compile command/binary needed to run a python program. What is the difference then, between java and python? Well, Java's compiler is more strict and sophisticated. As you might know Java is a statically typed language. So the compiler is written in a way that it can verify types related errors during compile time. While python being a dynamic language, types are not known until a program is run. So in a way, python compiler is dumb (or, less strict). But there indeed is a compile step involved when a python program is run. You might have seen python bytecode files with .pyc extension. Here is how you can see bytecode for a given python program. # Create a Hello World spatel1-mn1:tmp spatel1$ echo print('hello world') hello_world.py # Making sure it runs spatel1-mn1:tmp spatel1$ python3 hello_world.py hello world # The bytecode of the given program spatel1-mn1:tmp spatel1$ python -m dis hello_world.py 1 0 LOAD_NAME 0 (print) 2 LOAD_CONST 0 ('hello world') 4 CALL_FUNCTION 1 6 POP_TOP 8 LOAD_CONST 1 (None) 10 RETURN_VALUE Read more about dis module here Now coming to C/C++, there of course is a compiler. But the output is different than what java/python compiler would produce. Compiling a C program would produce what we also know as machine code . As opposed to bytecode.","title":"Compiled vs. Interpreted"},{"location":"python_web/intro/#running-the-programs","text":"We know compilation is involved in all 3 languages we are discussing. Just that the compilers are different in nature and they output different types of content. In case of C/C++, the output is machine code which can be directly read by your operating system. When you execute that program, your OS will know how exactly to run it. But this is not the case with bytecode. Those bytecodes are language specific. Python has its own set of bytecode defined (more in dis module) and so does java. So naturally, your operating system will not know how to run it. To run this bytecode, we have something called Virtual Machines. Ie: The JVM or the Python VM (CPython, Jython). These so called Virtual Machines are the programs which can read the bytecode and run it on a given operating system. Python has multiple VMs available. Cpython is a python VM implemented in C language, similarly Jython is a Java implementation of python VM. At the end of the day, what they should be capable of is to understand python language syntax, be able to compile it to bytecode and be able to run that bytecode. You can implement a python VM in any language! (And people do so, just because it can be done) The Operating System +------------------------------------+ | | | | | | hello_world.py Python bytecode | Python VM Process | | | +----------------+ +----------------+ | +----------------+ | |print(... | COMPILE |LOAD_CONST... | | |Reads bytecode | | | +--------------- + +------------------- +line by line | | | | | | | |and executes. | | | | | | | | | | +----------------+ +----------------+ | +----------------+ | | | | | | | hello_world.c OS Specific machinecode | A New Process | | | +----------------+ +----------------+ | +----------------+ | |void main() { | COMPILE | binary contents| | | binary contents| | | +--------------- + +------------------- + | | | | | | | | | | | | | | | | | | +----------------+ +----------------+ | +----------------+ | | (binary contents | | runs as is) | | | | | +------------------------------------+ Two things to note for above diagram: Generally, when we run a python program, a python VM process is started which reads the python source code, compiles it to byte code and run it in a single step. Compiling is not a separate step. Shown only for illustration purpose. Binaries generated for C like languages are not exactly run as is. Since there are multiple types of binaries (eg: ELF), there are more complicated steps involved in order to run a binary but we will not go into that since all that is done at OS level.","title":"Running The Programs"},{"location":"python_web/python-concepts/","text":"Some Python Concepts Though you are expected to know python and its syntax at basic level, let us discuss some fundamental concepts that will help you understand the python language better. Everything in Python is an object. That includes the functions, lists, dicts, classes, modules, a running function (instance of function definition), everything. In the CPython, it would mean there is an underlying struct variable for each object. In python's current execution context, all the variables are stored in a dict. It'd be a string to object mapping. If you have a function and a float variable defined in the current context, here is how it is handled internally. float_number=42.0 def foo_func(): ... pass ... # NOTICE HOW VARIABLE NAMES ARE STRINGS, stored in a dict locals() {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': class '_frozen_importlib.BuiltinImporter' , '__spec__': None, '__annotations__': {}, '__builtins__': module 'builtins' (built-in) , 'float_number': 42.0, 'foo_func': function foo_func at 0x1055847a0 } Python Functions Since functions too are objects, we can see what all attributes a function contains as following def hello(name): ... print(f Hello, {name}! ) ... dir(hello) ['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__'] While there are a lot of them, let's look at some interesting ones globals This attribute, as the name suggests, has references of global variables. If you ever need to know what all global variables are in the scope of this function, this will tell you. See how the function start seeing the new variable in globals hello.__globals__ {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': class '_frozen_importlib.BuiltinImporter' , '__spec__': None, '__annotations__': {}, '__builtins__': module 'builtins' (built-in) , 'hello': function hello at 0x7fe4e82554c0 } # adding new global variable GLOBAL= g_val hello.__globals__ {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': class '_frozen_importlib.BuiltinImporter' , '__spec__': None, '__annotations__': {}, '__builtins__': module 'builtins' (built-in) , 'hello': function hello at 0x7fe4e82554c0 , 'GLOBAL': 'g_val'} code This is an interesting one! As everything in python is an object, this includes the bytecode too. The compiled python bytecode is a python code object. Which is accessible via __code__ attribute here. A function has an associated code object which carries some interesting information. # the file in which function is defined # stdin here since this is run in an interpreter hello.__code__.co_filename ' stdin ' # number of arguments the function takes hello.__code__.co_argcount 1 # local variable names hello.__code__.co_varnames ('name',) # the function code's compiled bytecode hello.__code__.co_code b't\\x00d\\x01|\\x00\\x9b\\x00d\\x02\\x9d\\x03\\x83\\x01\\x01\\x00d\\x00S\\x00' There are more code attributes which you can enlist by dir(hello.__code__) Decorators Related to functions, python has another feature called decorators. Let's see how that works, keeping everything is an object in mind. Here is a sample decorator: def deco(func): ... def inner(): ... print( before ) ... func() ... print( after ) ... return inner ... @deco ... def hello_world(): ... print( hello world ) ... hello_world() before hello world after Here @deco syntax is used to decorate the hello_world function. It is essentially same as doing def hello_world(): ... print( hello world ) ... hello_world = deco(hello_world) What goes inside the deco function might seem complex. Let's try to uncover it. Function hello_world is created It is passed to deco function deco create a new function This new function is calls hello_world function And does a couple other things deco returns the newly created function hello_world is replaced with above function Let's visualize it for better understanding BEFORE function_object (ID: 100) hello_world +--------------------+ + |print( hello_world )| | | | +-------------- | | | | +--------------------+ WHAT DECORATOR DOES creates a new function (ID: 101) +---------------------------------+ |input arg: function with id: 100 | | | |print( before ) | |call function object with id 100 | |print( after ) | | | +---------------------------^-----+ | | AFTER | | | hello_world +-------------+ Note how the hello_world name points to a new function object but that new function object knows the reference (ID) of the original function. Some Gotchas While it is very quick to build prototypes in python and there are tons of libraries available, as the codebase complexity increases, type errors become more common and will get hard to deal with. (There are solutions to that problem like type annotations in python. Checkout mypy .) Because python is dynamically typed language, that means all types are determined at runtime. And that makes python run very slow compared to other statically typed languages. Python has something called GIL (global interpreter lock) which is a limiting factor for utilizing multiple CPI cores for parallel computation. Some weird things that python does: https://github.com/satwikkansal/wtfpython","title":"Some Python Concepts"},{"location":"python_web/python-concepts/#some-python-concepts","text":"Though you are expected to know python and its syntax at basic level, let us discuss some fundamental concepts that will help you understand the python language better. Everything in Python is an object. That includes the functions, lists, dicts, classes, modules, a running function (instance of function definition), everything. In the CPython, it would mean there is an underlying struct variable for each object. In python's current execution context, all the variables are stored in a dict. It'd be a string to object mapping. If you have a function and a float variable defined in the current context, here is how it is handled internally. float_number=42.0 def foo_func(): ... pass ... # NOTICE HOW VARIABLE NAMES ARE STRINGS, stored in a dict locals() {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': class '_frozen_importlib.BuiltinImporter' , '__spec__': None, '__annotations__': {}, '__builtins__': module 'builtins' (built-in) , 'float_number': 42.0, 'foo_func': function foo_func at 0x1055847a0 }","title":"Some Python Concepts"},{"location":"python_web/python-concepts/#python-functions","text":"Since functions too are objects, we can see what all attributes a function contains as following def hello(name): ... print(f Hello, {name}! ) ... dir(hello) ['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__'] While there are a lot of them, let's look at some interesting ones","title":"Python Functions"},{"location":"python_web/python-concepts/#globals","text":"This attribute, as the name suggests, has references of global variables. If you ever need to know what all global variables are in the scope of this function, this will tell you. See how the function start seeing the new variable in globals hello.__globals__ {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': class '_frozen_importlib.BuiltinImporter' , '__spec__': None, '__annotations__': {}, '__builtins__': module 'builtins' (built-in) , 'hello': function hello at 0x7fe4e82554c0 } # adding new global variable GLOBAL= g_val hello.__globals__ {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': class '_frozen_importlib.BuiltinImporter' , '__spec__': None, '__annotations__': {}, '__builtins__': module 'builtins' (built-in) , 'hello': function hello at 0x7fe4e82554c0 , 'GLOBAL': 'g_val'}","title":"globals"},{"location":"python_web/python-concepts/#code","text":"This is an interesting one! As everything in python is an object, this includes the bytecode too. The compiled python bytecode is a python code object. Which is accessible via __code__ attribute here. A function has an associated code object which carries some interesting information. # the file in which function is defined # stdin here since this is run in an interpreter hello.__code__.co_filename ' stdin ' # number of arguments the function takes hello.__code__.co_argcount 1 # local variable names hello.__code__.co_varnames ('name',) # the function code's compiled bytecode hello.__code__.co_code b't\\x00d\\x01|\\x00\\x9b\\x00d\\x02\\x9d\\x03\\x83\\x01\\x01\\x00d\\x00S\\x00' There are more code attributes which you can enlist by dir(hello.__code__)","title":"code"},{"location":"python_web/python-concepts/#decorators","text":"Related to functions, python has another feature called decorators. Let's see how that works, keeping everything is an object in mind. Here is a sample decorator: def deco(func): ... def inner(): ... print( before ) ... func() ... print( after ) ... return inner ... @deco ... def hello_world(): ... print( hello world ) ... hello_world() before hello world after Here @deco syntax is used to decorate the hello_world function. It is essentially same as doing def hello_world(): ... print( hello world ) ... hello_world = deco(hello_world) What goes inside the deco function might seem complex. Let's try to uncover it. Function hello_world is created It is passed to deco function deco create a new function This new function is calls hello_world function And does a couple other things deco returns the newly created function hello_world is replaced with above function Let's visualize it for better understanding BEFORE function_object (ID: 100) hello_world +--------------------+ + |print( hello_world )| | | | +-------------- | | | | +--------------------+ WHAT DECORATOR DOES creates a new function (ID: 101) +---------------------------------+ |input arg: function with id: 100 | | | |print( before ) | |call function object with id 100 | |print( after ) | | | +---------------------------^-----+ | | AFTER | | | hello_world +-------------+ Note how the hello_world name points to a new function object but that new function object knows the reference (ID) of the original function.","title":"Decorators"},{"location":"python_web/python-concepts/#some-gotchas","text":"While it is very quick to build prototypes in python and there are tons of libraries available, as the codebase complexity increases, type errors become more common and will get hard to deal with. (There are solutions to that problem like type annotations in python. Checkout mypy .) Because python is dynamically typed language, that means all types are determined at runtime. And that makes python run very slow compared to other statically typed languages. Python has something called GIL (global interpreter lock) which is a limiting factor for utilizing multiple CPI cores for parallel computation. Some weird things that python does: https://github.com/satwikkansal/wtfpython","title":"Some Gotchas"},{"location":"python_web/python-web-flask/","text":"Python, Web amd Flask Back in the old days, websites were simple. They were simple static html contents. A webserver would be listening on a defined port and according to the HTTP request received, it would read files from disk and return them in response. But since then, complexity has evolved and websites are now dynamic. Depending on the request, multiple operations need to be performed like reading from database or calling other API and finally returning some response (HTML data, JSON content etc.) Since serving web requests is no longer a simple task like reading files from disk and return contents, we need to process each http request, perform some operations programmatically and construct a response. Sockets Though we have frameworks like flask, HTTP is still a protocol that works over TCP protocol. So let us setup a TCP server and send an HTTP request and inspect the request's payload. Note that this is not a tutorial on socket programming but what we are doing here is inspecting HTTP protocol at its ground level and look at what its contents look like. (Ref: Socket Programming in Python (Guide) on RealPython ) import socket HOST = '127.0.0.1' # Standard loopback interface address (localhost) PORT = 65432 # Port to listen on (non-privileged ports are 1023) with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.bind((HOST, PORT)) s.listen() conn, addr = s.accept() with conn: print('Connected by', addr) while True: data = conn.recv(1024) if not data: break print(data) Then we open localhost:65432 in our web browser and following would be the output: Connected by ('127.0.0.1', 54719) b'GET / HTTP/1.1\\r\\nHost: localhost:65432\\r\\nConnection: keep-alive\\r\\nDNT: 1\\r\\nUpgrade-Insecure-Requests: 1\\r\\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36 Edg/85.0.564.44\\r\\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\\r\\nSec-Fetch-Site: none\\r\\nSec-Fetch-Mode: navigate\\r\\nSec-Fetch-User: ?1\\r\\nSec-Fetch-Dest: document\\r\\nAccept-Encoding: gzip, deflate, br\\r\\nAccept-Language: en-US,en;q=0.9\\r\\n\\r\\n' Examine closely and the content will look like the HTTP protocol's format. ie: HTTP_METHOD URI_PATH HTTP_VERSION HEADERS_SEPARATED_BY_SEPARATOR So though it's a blob of bytes, knowing http protocol specification , you can parse that string (ie: split by \\r\\n ) and get meaningful information out of it. Flask Flask, and other such frameworks does pretty much what we just discussed in the last section (with added more sophistication). They listen on a port on a TCP socket, receive an HTTP request, parse the data according to protocol format and make it available to you in a convenient manner. ie: you can access headers in flask by request.headers which is made available to you by splitting above payload by /r/n , as defined in http protocol. Another example: we register routes in flask by @app.route(\"/hello\") . What flask will do is maintain a registry internally which will map /hello with the function you decorated with. Now whenever a request comes with the /hello route (second component in the first line, split by space), flask calls the registered function and returns whatever the function returned. Same with all other web frameworks in other languages too. They all work on similar principles. What they basically do is understand the HTTP protocol, parses the HTTP request data and gives us programmers a nice interface to work with HTTP requests. Not so much of magic, innit?","title":"Python, Web and Flask"},{"location":"python_web/python-web-flask/#python-web-amd-flask","text":"Back in the old days, websites were simple. They were simple static html contents. A webserver would be listening on a defined port and according to the HTTP request received, it would read files from disk and return them in response. But since then, complexity has evolved and websites are now dynamic. Depending on the request, multiple operations need to be performed like reading from database or calling other API and finally returning some response (HTML data, JSON content etc.) Since serving web requests is no longer a simple task like reading files from disk and return contents, we need to process each http request, perform some operations programmatically and construct a response.","title":"Python, Web amd Flask"},{"location":"python_web/python-web-flask/#sockets","text":"Though we have frameworks like flask, HTTP is still a protocol that works over TCP protocol. So let us setup a TCP server and send an HTTP request and inspect the request's payload. Note that this is not a tutorial on socket programming but what we are doing here is inspecting HTTP protocol at its ground level and look at what its contents look like. (Ref: Socket Programming in Python (Guide) on RealPython ) import socket HOST = '127.0.0.1' # Standard loopback interface address (localhost) PORT = 65432 # Port to listen on (non-privileged ports are 1023) with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.bind((HOST, PORT)) s.listen() conn, addr = s.accept() with conn: print('Connected by', addr) while True: data = conn.recv(1024) if not data: break print(data) Then we open localhost:65432 in our web browser and following would be the output: Connected by ('127.0.0.1', 54719) b'GET / HTTP/1.1\\r\\nHost: localhost:65432\\r\\nConnection: keep-alive\\r\\nDNT: 1\\r\\nUpgrade-Insecure-Requests: 1\\r\\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36 Edg/85.0.564.44\\r\\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\\r\\nSec-Fetch-Site: none\\r\\nSec-Fetch-Mode: navigate\\r\\nSec-Fetch-User: ?1\\r\\nSec-Fetch-Dest: document\\r\\nAccept-Encoding: gzip, deflate, br\\r\\nAccept-Language: en-US,en;q=0.9\\r\\n\\r\\n' Examine closely and the content will look like the HTTP protocol's format. ie: HTTP_METHOD URI_PATH HTTP_VERSION HEADERS_SEPARATED_BY_SEPARATOR So though it's a blob of bytes, knowing http protocol specification , you can parse that string (ie: split by \\r\\n ) and get meaningful information out of it.","title":"Sockets"},{"location":"python_web/python-web-flask/#flask","text":"Flask, and other such frameworks does pretty much what we just discussed in the last section (with added more sophistication). They listen on a port on a TCP socket, receive an HTTP request, parse the data according to protocol format and make it available to you in a convenient manner. ie: you can access headers in flask by request.headers which is made available to you by splitting above payload by /r/n , as defined in http protocol. Another example: we register routes in flask by @app.route(\"/hello\") . What flask will do is maintain a registry internally which will map /hello with the function you decorated with. Now whenever a request comes with the /hello route (second component in the first line, split by space), flask calls the registered function and returns whatever the function returned. Same with all other web frameworks in other languages too. They all work on similar principles. What they basically do is understand the HTTP protocol, parses the HTTP request data and gives us programmers a nice interface to work with HTTP requests. Not so much of magic, innit?","title":"Flask"},{"location":"python_web/sre-conclusion/","text":"SRE Parts of The App and Conclusion Scaling The App The design and development is just a part of the journey. We will need to setup continuous integration and continuous delivery pipelines sooner or later. And we have to deploy this app somewhere. Initially we can start with deploying this app on one virtual machine on any cloud provider. But this is a Single point of failure which is something we never allow as an SRE (or even as an engineer). So an improvement here can be having multiple instances of applications deployed behind a load balancer. This certainly prevents problems of one machine going down. Scaling here would mean adding more instances behind the load balancer. But this is scalable upto only a certain point. After that, other bottlenecks in the system will start appearing. ie: DB will become the bottleneck, or perhaps the load balancer itself. How do you know what is the bottleneck? You need to have observability into each aspects of the application architecture. Only after you have metrics, you will be able to know what is going wrong where. What gets measured, gets fixed! Get deeper insights into scaling from School Of SRE's Scalability module and post going through it, apply your learnings and takeaways to this app. Think how will we make this app geographically distributed and highly available and scalable. Monitoring Strategy Once we have our application deployed. It will be working ok. But not forever. Reliability is in the title of our job and we make systems reliable by making the design in a certain way. But things still will go down. Machines will fail. Disks will behave weirdly. Buggy code will get pushed to production. And all these possible scenarios will make the system less reliable. So what do we do? We monitor! We keep an eye on the system's health and if anything is not going as expected, we want ourselves to get alerted. Now let's think in terms of the given url shortening app. We need to monitor it. And we would want to get notified in case something goes wrong. But we first need to decide what is that something that we want to keep an eye on. Since it's a web app serving HTTP requests, we want to keep an eye on HTTP Status codes and latencies Request volume again is a good candidate, if the app is receiving an unusual amount of traffic, something might be off. We also want to keep an eye on the database so depending on the database solution chosen. Query times, volumes, disk usage etc. Finally, there also needs to be some external monitoring which runs periodic tests from devices outside of your data centers. This emulates customers and ensures that from customer point of view, the system is working as expected. SRE Use-cases In the world of SRE, python is a widely used language. For small scripts and tooling developed for various purposes. Since tooling developed by SRE works with critical pieces of infrastructure and has great power (to bring things down), it is important to know what you are doing while using a programming language and its features. Also it is equally important to know the language and its characteristics while debugging the issues. As an SRE having a deeper understanding of python language, it has helped me a lot to debug very sneaky bugs and be generally more aware and informed while making certain design decisions. While developing tools may or may not be part of SRE job, supporting tools or services is more likely to be a daily duty. Building an application or tool is just a small part of productionization. While there is certainly that goes in the design of the application itself to make it more robust, as an SRE you are responsible for its reliability and stability once it is deployed and running. And to ensure that, you\u2019d need to understand the application first and then come up with a strategy to monitor it properly and be prepared for various failure scenarios. Optional Exercises Make a decorator that will cache function return values depending on input parameters. Host the URL shortening app on any cloud provider. Setup monitoring using many of the tools available like catchpoint, datadog etc. Create a minimal flask-like framework on top of TCP sockets. Conclusion This module, in the first part, aims to make you more aware of the things that will happen when you choose python as your programming language and what happens when you run a python program. With the knowledge of how python handles things internally as objects, lot of seemingly magic things in python will start to make more sense. The second part will first explain how a framework like flask works using the existing knowledge of protocols like TCP and HTTP. It then touches the whole lifecycle of an application development lifecycle including the SRE parts of it. While the design and areas in architecture considered will not be exhaustive, it will give a good overview of things that are also important being an SRE and why they are important.","title":"SRE Aspects of The App and Conclusion"},{"location":"python_web/sre-conclusion/#sre-parts-of-the-app-and-conclusion","text":"","title":"SRE Parts of The App and Conclusion"},{"location":"python_web/sre-conclusion/#scaling-the-app","text":"The design and development is just a part of the journey. We will need to setup continuous integration and continuous delivery pipelines sooner or later. And we have to deploy this app somewhere. Initially we can start with deploying this app on one virtual machine on any cloud provider. But this is a Single point of failure which is something we never allow as an SRE (or even as an engineer). So an improvement here can be having multiple instances of applications deployed behind a load balancer. This certainly prevents problems of one machine going down. Scaling here would mean adding more instances behind the load balancer. But this is scalable upto only a certain point. After that, other bottlenecks in the system will start appearing. ie: DB will become the bottleneck, or perhaps the load balancer itself. How do you know what is the bottleneck? You need to have observability into each aspects of the application architecture. Only after you have metrics, you will be able to know what is going wrong where. What gets measured, gets fixed! Get deeper insights into scaling from School Of SRE's Scalability module and post going through it, apply your learnings and takeaways to this app. Think how will we make this app geographically distributed and highly available and scalable.","title":"Scaling The App"},{"location":"python_web/sre-conclusion/#monitoring-strategy","text":"Once we have our application deployed. It will be working ok. But not forever. Reliability is in the title of our job and we make systems reliable by making the design in a certain way. But things still will go down. Machines will fail. Disks will behave weirdly. Buggy code will get pushed to production. And all these possible scenarios will make the system less reliable. So what do we do? We monitor! We keep an eye on the system's health and if anything is not going as expected, we want ourselves to get alerted. Now let's think in terms of the given url shortening app. We need to monitor it. And we would want to get notified in case something goes wrong. But we first need to decide what is that something that we want to keep an eye on. Since it's a web app serving HTTP requests, we want to keep an eye on HTTP Status codes and latencies Request volume again is a good candidate, if the app is receiving an unusual amount of traffic, something might be off. We also want to keep an eye on the database so depending on the database solution chosen. Query times, volumes, disk usage etc. Finally, there also needs to be some external monitoring which runs periodic tests from devices outside of your data centers. This emulates customers and ensures that from customer point of view, the system is working as expected.","title":"Monitoring Strategy"},{"location":"python_web/sre-conclusion/#sre-use-cases","text":"In the world of SRE, python is a widely used language. For small scripts and tooling developed for various purposes. Since tooling developed by SRE works with critical pieces of infrastructure and has great power (to bring things down), it is important to know what you are doing while using a programming language and its features. Also it is equally important to know the language and its characteristics while debugging the issues. As an SRE having a deeper understanding of python language, it has helped me a lot to debug very sneaky bugs and be generally more aware and informed while making certain design decisions. While developing tools may or may not be part of SRE job, supporting tools or services is more likely to be a daily duty. Building an application or tool is just a small part of productionization. While there is certainly that goes in the design of the application itself to make it more robust, as an SRE you are responsible for its reliability and stability once it is deployed and running. And to ensure that, you\u2019d need to understand the application first and then come up with a strategy to monitor it properly and be prepared for various failure scenarios.","title":"SRE Use-cases"},{"location":"python_web/sre-conclusion/#optional-exercises","text":"Make a decorator that will cache function return values depending on input parameters. Host the URL shortening app on any cloud provider. Setup monitoring using many of the tools available like catchpoint, datadog etc. Create a minimal flask-like framework on top of TCP sockets.","title":"Optional Exercises"},{"location":"python_web/sre-conclusion/#conclusion","text":"This module, in the first part, aims to make you more aware of the things that will happen when you choose python as your programming language and what happens when you run a python program. With the knowledge of how python handles things internally as objects, lot of seemingly magic things in python will start to make more sense. The second part will first explain how a framework like flask works using the existing knowledge of protocols like TCP and HTTP. It then touches the whole lifecycle of an application development lifecycle including the SRE parts of it. While the design and areas in architecture considered will not be exhaustive, it will give a good overview of things that are also important being an SRE and why they are important.","title":"Conclusion"},{"location":"python_web/url-shorten-app/","text":"The URL Shortening App Let's build a very simple URL shortening app using flask and try to incorporate all aspects of the development process including the reliability aspects. We will not be building the UI and we will come up with a minimal set of API that will be enough for the app to function well. Design We don't jump directly to coding. First thing we do is gather requirements. Come up with an approach. Have the approach/design reviewed by peers. Evolve, iterate, document the decisions and tradeoffs. And then finally implement. While we will not do the full blown design document here, we will raise certain questions here that are important to the design. 1. High Level Operations and API Endpoints Since it's a URL shortening app, we will need an API for generating the shorten link given an original link. And an API/Endpoint which will accept the shorten link and redirect to original URL. We are not including the user aspect of the app to keep things minimal. These two API should make app functional and usable by anyone. 2. How to shorten? Given a url, we will need to generate a shortened version of it. One approach could be using random characters for each link. Another thing that can be done is to use some sort of hashing algorithm. The benefit here is we will reuse the same hash for the same link. ie: if lot of people are shortening https://www.linkedin.com they all will have the same value, compared to multiple entries in DB if chosen random characters. What about hash collisions? Even in random characters approach, though there is a less probability, hash collisions can happen. And we need to be mindful of them. In that case we might want to prepend/append the string with some random value to avoid conflict. Also, choice of hash algorithm matters. We will need to analyze algorithms. Their CPU requirements and their characteristics. Choose one that suits the most. 3. Is URL Valid? Given a URL to shorten, how do we verify if the URL is valid? Do we even verify or validate? One basic check that can be done is see if the URL matches a regex of a URL. To go even further we can try opening/visiting the URL. But there are certain gotchas here. We need to define success criteria. ie: HTTP 200 means it is valid. What is the URL is in private network? What if URL is temporarily down? 4. Storage Finally, storage. Where will we store the data that we will generate over time? There are multiple database solutions available and we will need to choose the one that suits this app the most. Relational database like MySQL would be a fair choice but be sure to checkout School of SRE's database section for deeper insights into making a more informed decision. 5. Other We are not accounting for users into our app and other possible features like rate limiting, customized links etc but it will eventually come up with time. Depending on the requirements, they too might need to get incorporated. The minimal working code is given below for reference but I'd encourage you to come up with your own. from flask import Flask, redirect, request from hashlib import md5 app = Flask( url_shortener ) mapping = {} @app.route( /shorten , methods=[ POST ]) def shorten(): global mapping payload = request.json if url not in payload: return Missing URL Parameter , 400 # TODO: check if URL is valid hash_ = md5() hash_.update(payload[ url ].encode()) digest = hash_.hexdigest()[:5] # limiting to 5 chars. Less the limit more the chances of collission if digest not in mapping: mapping[digest] = payload[ url ] return f Shortened: r/{digest}\\n else: # TODO: check for hash collission return f Already exists: r/{digest}\\n @app.route( /r/ hash_ ) def redirect_(hash_): if hash_ not in mapping: return URL Not Found , 404 return redirect(mapping[hash_]) if __name__ == __main__ : app.run(debug=True) OUTPUT: === SHORTENING spatel1-mn1:tmp spatel1$ curl localhost:5000/shorten -H content-type: application/json --data '{ url : https://linkedin.com }' Shortened: r/a62a4 === REDIRECTING, notice the response code 302 and the location header spatel1-mn1:tmp spatel1$ curl localhost:5000/r/a62a4 -v * Uses proxy env variable NO_PROXY == '127.0.0.1' * Trying ::1... * TCP_NODELAY set * Connection failed * connect to ::1 port 5000 failed: Connection refused * Trying 127.0.0.1... * TCP_NODELAY set * Connected to localhost (127.0.0.1) port 5000 (#0) GET /r/a62a4 HTTP/1.1 Host: localhost:5000 User-Agent: curl/7.64.1 Accept: */* * HTTP 1.0, assume close after body HTTP/1.0 302 FOUND Content-Type: text/html; charset=utf-8 Content-Length: 247 Location: https://linkedin.com Server: Werkzeug/0.15.4 Python/3.7.7 Date: Tue, 27 Oct 2020 09:37:12 GMT !DOCTYPE HTML PUBLIC -//W3C//DTD HTML 3.2 Final//EN title Redirecting... /title h1 Redirecting... /h1 * Closing connection 0 p You should be redirected automatically to target URL: a href= https://linkedin.com https://linkedin.com /a . If not click the link.","title":"The URL Shortening App"},{"location":"python_web/url-shorten-app/#the-url-shortening-app","text":"Let's build a very simple URL shortening app using flask and try to incorporate all aspects of the development process including the reliability aspects. We will not be building the UI and we will come up with a minimal set of API that will be enough for the app to function well.","title":"The URL Shortening App"},{"location":"python_web/url-shorten-app/#design","text":"We don't jump directly to coding. First thing we do is gather requirements. Come up with an approach. Have the approach/design reviewed by peers. Evolve, iterate, document the decisions and tradeoffs. And then finally implement. While we will not do the full blown design document here, we will raise certain questions here that are important to the design.","title":"Design"},{"location":"python_web/url-shorten-app/#1-high-level-operations-and-api-endpoints","text":"Since it's a URL shortening app, we will need an API for generating the shorten link given an original link. And an API/Endpoint which will accept the shorten link and redirect to original URL. We are not including the user aspect of the app to keep things minimal. These two API should make app functional and usable by anyone.","title":"1. High Level Operations and API Endpoints"},{"location":"python_web/url-shorten-app/#2-how-to-shorten","text":"Given a url, we will need to generate a shortened version of it. One approach could be using random characters for each link. Another thing that can be done is to use some sort of hashing algorithm. The benefit here is we will reuse the same hash for the same link. ie: if lot of people are shortening https://www.linkedin.com they all will have the same value, compared to multiple entries in DB if chosen random characters. What about hash collisions? Even in random characters approach, though there is a less probability, hash collisions can happen. And we need to be mindful of them. In that case we might want to prepend/append the string with some random value to avoid conflict. Also, choice of hash algorithm matters. We will need to analyze algorithms. Their CPU requirements and their characteristics. Choose one that suits the most.","title":"2. How to shorten?"},{"location":"python_web/url-shorten-app/#3-is-url-valid","text":"Given a URL to shorten, how do we verify if the URL is valid? Do we even verify or validate? One basic check that can be done is see if the URL matches a regex of a URL. To go even further we can try opening/visiting the URL. But there are certain gotchas here. We need to define success criteria. ie: HTTP 200 means it is valid. What is the URL is in private network? What if URL is temporarily down?","title":"3. Is URL Valid?"},{"location":"python_web/url-shorten-app/#4-storage","text":"Finally, storage. Where will we store the data that we will generate over time? There are multiple database solutions available and we will need to choose the one that suits this app the most. Relational database like MySQL would be a fair choice but be sure to checkout School of SRE's database section for deeper insights into making a more informed decision.","title":"4. Storage"},{"location":"python_web/url-shorten-app/#5-other","text":"We are not accounting for users into our app and other possible features like rate limiting, customized links etc but it will eventually come up with time. Depending on the requirements, they too might need to get incorporated. The minimal working code is given below for reference but I'd encourage you to come up with your own. from flask import Flask, redirect, request from hashlib import md5 app = Flask( url_shortener ) mapping = {} @app.route( /shorten , methods=[ POST ]) def shorten(): global mapping payload = request.json if url not in payload: return Missing URL Parameter , 400 # TODO: check if URL is valid hash_ = md5() hash_.update(payload[ url ].encode()) digest = hash_.hexdigest()[:5] # limiting to 5 chars. Less the limit more the chances of collission if digest not in mapping: mapping[digest] = payload[ url ] return f Shortened: r/{digest}\\n else: # TODO: check for hash collission return f Already exists: r/{digest}\\n @app.route( /r/ hash_ ) def redirect_(hash_): if hash_ not in mapping: return URL Not Found , 404 return redirect(mapping[hash_]) if __name__ == __main__ : app.run(debug=True) OUTPUT: === SHORTENING spatel1-mn1:tmp spatel1$ curl localhost:5000/shorten -H content-type: application/json --data '{ url : https://linkedin.com }' Shortened: r/a62a4 === REDIRECTING, notice the response code 302 and the location header spatel1-mn1:tmp spatel1$ curl localhost:5000/r/a62a4 -v * Uses proxy env variable NO_PROXY == '127.0.0.1' * Trying ::1... * TCP_NODELAY set * Connection failed * connect to ::1 port 5000 failed: Connection refused * Trying 127.0.0.1... * TCP_NODELAY set * Connected to localhost (127.0.0.1) port 5000 (#0) GET /r/a62a4 HTTP/1.1 Host: localhost:5000 User-Agent: curl/7.64.1 Accept: */* * HTTP 1.0, assume close after body HTTP/1.0 302 FOUND Content-Type: text/html; charset=utf-8 Content-Length: 247 Location: https://linkedin.com Server: Werkzeug/0.15.4 Python/3.7.7 Date: Tue, 27 Oct 2020 09:37:12 GMT !DOCTYPE HTML PUBLIC -//W3C//DTD HTML 3.2 Final//EN title Redirecting... /title h1 Redirecting... /h1 * Closing connection 0 p You should be redirected automatically to target URL: a href= https://linkedin.com https://linkedin.com /a . If not click the link.","title":"5. Other"},{"location":"systems_design/availability/","text":"HA - Availability - Common \u201cNines\u201d Availability is generally expressed as \u201cNines\u201d, common \u2018Nines\u2019 are listed below. Availability % Downtime per year Downtime per month Downtime per week Downtime per day 99%(Two Nines) 3.65 days 7.31 hours 1.68 hours 14.40 minutes 99.5%(Two and a half Nines) 1.83 days 3.65 hours 50.40 minutes 7.20 minutes 99.9%(Three Nines) 8.77 hours 43.83 minutes 10.08 minutes 1.44 minutes 99.95%(Three and a half Nines) 4.38 hours 21.92 minutes 5.04 minutes 43.20 seconds 99.99%(Four Nines) 52.60 minutes 4.38 minutes 1.01 minutes 8.64 seconds 99.995%(Four and a half Nines) 26.30 minutes 2.19 minutes 30.24 seconds 4.32 seconds 99.999%(Five Nines) 5.26 minutes 26.30 seconds 6.05 seconds 864.0 ms Refer https://en.wikipedia.org/wiki/High_availability#Percentage_calculation HA - Availability Serial Components A System with components is operating in the series If failure of a part leads to the combination becoming inoperable. For example if LB in our architecture fails, all access to app tiers will fail. LB and app tiers are connected serially. The combined availability of the system is the product of individual components availability A = Ax x Ay x \u2026.. Refer http://www.eventhelix.com/RealtimeMantra/FaultHandling/system_reliability_availability.htm HA - Availability Parallel Components A System with components is operating in parallel If failure of a part leads to the other part taking over the operations of the failed part. If we have more than one LB and if rest of the LBs can take over the traffic during one LB failure then LBs are operating in parallel The combined availability of the system is A = 1 - ( (1-Ax) x (1-Ax) x \u2026.. ) Refer http://www.eventhelix.com/RealtimeMantra/FaultHandling/system_reliability_availability.htm HA - Core Principles Elimination of single points of failure (SPOF) This means adding redundancy to the system so that the failure of a component does not mean failure of the entire system. Reliable crossover In redundant systems, the crossover point itself tends to become a single point of failure. Reliable systems must provide for reliable crossover. Detection of failures as they occur If the two principles above are observed, then a user may never see a failure Refer https://en.wikipedia.org/wiki/High_availability#Principles HA - SPOF WHAT: Never implement and always eliminate single points of failure. WHEN TO USE: During architecture reviews and new designs. HOW TO USE: Identify single instances on architectural diagrams. Strive for active/active configurations. At the very least we should have a standby to take control when active instances fail. WHY: Maximize availability through multiple instances. KEY TAKEAWAYS: Strive for active/active rather than active/passive solutions. Use load balancers to balance traffic across instances of a service. Use control services with active/passive instances for patterns that require singletons. HA - Reliable Crossover WHAT: Ensure when system components failover they do so reliably. WHEN TO USE: During architecture reviews, failure modeling, and designs. HOW TO USE: Identify how available a system is during the crossover and ensure it is within acceptable limits. WHY: Maximize availability and ensure data handling semantics are preserved. KEY TAKEAWAYS: Strive for active/active rather than active/passive solutions, they have a lesser risk of cross over being unreliable. Use LB and right load balancing methods to ensure reliable failover. Model and build your data systems to ensure data is correctly handled when crossover happens. Generally DB systems follow active/passive semantics for writes. Masters accept writes and when master goes down, follower is promoted to master(active from being passive) to accept writes. We have to be careful here that the cutover never introduces more than one masters. This problem is called a split brain. SRE Use cases SRE works on deciding an acceptable SLA and make sure system is available to achieve the SLA SRE is involved in architecture design right from building the data center to make sure site is not affected by network switch, hardware, power or software failures SRE also run mock drills of failures to see how the system behaves in uncharted territory and comes up with a plan to improve availability if there are misses. https://engineering.linkedin.com/blog/2017/11/resilience-engineering-at-linkedin-with-project-waterbear Post our understanding about HA, our architecture diagram looks something like this below","title":"Availability"},{"location":"systems_design/availability/#ha-availability-common-nines","text":"Availability is generally expressed as \u201cNines\u201d, common \u2018Nines\u2019 are listed below. Availability % Downtime per year Downtime per month Downtime per week Downtime per day 99%(Two Nines) 3.65 days 7.31 hours 1.68 hours 14.40 minutes 99.5%(Two and a half Nines) 1.83 days 3.65 hours 50.40 minutes 7.20 minutes 99.9%(Three Nines) 8.77 hours 43.83 minutes 10.08 minutes 1.44 minutes 99.95%(Three and a half Nines) 4.38 hours 21.92 minutes 5.04 minutes 43.20 seconds 99.99%(Four Nines) 52.60 minutes 4.38 minutes 1.01 minutes 8.64 seconds 99.995%(Four and a half Nines) 26.30 minutes 2.19 minutes 30.24 seconds 4.32 seconds 99.999%(Five Nines) 5.26 minutes 26.30 seconds 6.05 seconds 864.0 ms","title":"HA - Availability - Common \u201cNines\u201d"},{"location":"systems_design/availability/#refer","text":"https://en.wikipedia.org/wiki/High_availability#Percentage_calculation","title":"Refer"},{"location":"systems_design/availability/#ha-availability-serial-components","text":"A System with components is operating in the series If failure of a part leads to the combination becoming inoperable. For example if LB in our architecture fails, all access to app tiers will fail. LB and app tiers are connected serially. The combined availability of the system is the product of individual components availability A = Ax x Ay x \u2026..","title":"HA - Availability Serial Components"},{"location":"systems_design/availability/#refer_1","text":"http://www.eventhelix.com/RealtimeMantra/FaultHandling/system_reliability_availability.htm","title":"Refer"},{"location":"systems_design/availability/#ha-availability-parallel-components","text":"A System with components is operating in parallel If failure of a part leads to the other part taking over the operations of the failed part. If we have more than one LB and if rest of the LBs can take over the traffic during one LB failure then LBs are operating in parallel The combined availability of the system is A = 1 - ( (1-Ax) x (1-Ax) x \u2026.. )","title":"HA - Availability Parallel Components"},{"location":"systems_design/availability/#refer_2","text":"http://www.eventhelix.com/RealtimeMantra/FaultHandling/system_reliability_availability.htm","title":"Refer"},{"location":"systems_design/availability/#ha-core-principles","text":"Elimination of single points of failure (SPOF) This means adding redundancy to the system so that the failure of a component does not mean failure of the entire system. Reliable crossover In redundant systems, the crossover point itself tends to become a single point of failure. Reliable systems must provide for reliable crossover. Detection of failures as they occur If the two principles above are observed, then a user may never see a failure","title":"HA - Core Principles"},{"location":"systems_design/availability/#refer_3","text":"https://en.wikipedia.org/wiki/High_availability#Principles","title":"Refer"},{"location":"systems_design/availability/#ha-spof","text":"WHAT: Never implement and always eliminate single points of failure. WHEN TO USE: During architecture reviews and new designs. HOW TO USE: Identify single instances on architectural diagrams. Strive for active/active configurations. At the very least we should have a standby to take control when active instances fail. WHY: Maximize availability through multiple instances. KEY TAKEAWAYS: Strive for active/active rather than active/passive solutions. Use load balancers to balance traffic across instances of a service. Use control services with active/passive instances for patterns that require singletons.","title":"HA - SPOF"},{"location":"systems_design/availability/#ha-reliable-crossover","text":"WHAT: Ensure when system components failover they do so reliably. WHEN TO USE: During architecture reviews, failure modeling, and designs. HOW TO USE: Identify how available a system is during the crossover and ensure it is within acceptable limits. WHY: Maximize availability and ensure data handling semantics are preserved. KEY TAKEAWAYS: Strive for active/active rather than active/passive solutions, they have a lesser risk of cross over being unreliable. Use LB and right load balancing methods to ensure reliable failover. Model and build your data systems to ensure data is correctly handled when crossover happens. Generally DB systems follow active/passive semantics for writes. Masters accept writes and when master goes down, follower is promoted to master(active from being passive) to accept writes. We have to be careful here that the cutover never introduces more than one masters. This problem is called a split brain.","title":"HA - Reliable Crossover"},{"location":"systems_design/availability/#sre-use-cases","text":"SRE works on deciding an acceptable SLA and make sure system is available to achieve the SLA SRE is involved in architecture design right from building the data center to make sure site is not affected by network switch, hardware, power or software failures SRE also run mock drills of failures to see how the system behaves in uncharted territory and comes up with a plan to improve availability if there are misses. https://engineering.linkedin.com/blog/2017/11/resilience-engineering-at-linkedin-with-project-waterbear Post our understanding about HA, our architecture diagram looks something like this below","title":"SRE Use cases"},{"location":"systems_design/conclusion/","text":"Conclusion Armed with these principles, we hope the course will give a fresh perspective to design software systems. It might be over engineering to get all this on day zero. But some are really important from day 0 like eliminating single points of failure, making scalable services by just increasing replicas. As a bottleneck is reached, we can split code by services, shard data to scale. As the organisation matures, bringing in chaos engineering to measure how systems react to failure will help in designing robust software systems.","title":"Conclusion"},{"location":"systems_design/conclusion/#conclusion","text":"Armed with these principles, we hope the course will give a fresh perspective to design software systems. It might be over engineering to get all this on day zero. But some are really important from day 0 like eliminating single points of failure, making scalable services by just increasing replicas. As a bottleneck is reached, we can split code by services, shard data to scale. As the organisation matures, bringing in chaos engineering to measure how systems react to failure will help in designing robust software systems.","title":"Conclusion"},{"location":"systems_design/fault-tolerance/","text":"Fault Tolerance Failures are not avoidable in any system and will happen all the time, hence we need to build systems that can tolerate failures or recover from them. In systems, failure is the norm rather than the exception. \"Anything that can go wrong will go wrong\u201d -- Murphy\u2019s Law \u201cComplex systems contain changing mixtures of failures latent within them\u201d -- How Complex Systems Fail. Fault Tolerance - Failure Metrics Common failure metrics that get measured and tracked for any system. Mean time to repair (MTTR): The average time to repair and restore a failed system. Mean time between failures (MTBF): The average operational time between one device failure or system breakdown and the next. Mean time to failure (MTTF): The average time a device or system is expected to function before it fails. Mean time to detect (MTTD): The average time between the onset of a problem and when the organization detects it. Mean time to investigate (MTTI): The average time between the detection of an incident and when the organization begins to investigate its cause and solution. Mean time to restore service (MTRS): The average elapsed time from the detection of an incident until the affected system or component is again available to users. Mean time between system incidents (MTBSI): The average elapsed time between the detection of two consecutive incidents. MTBSI can be calculated by adding MTBF and MTRS (MTBSI = MTBF + MTRS). Failure rate: Another reliability metric, which measures the frequency with which a component or system fails. It is expressed as a number of failures over a unit of time. Refer https://www.splunk.com/en_us/data-insider/what-is-mean-time-to-repair.html Fault Tolerance - Fault Isolation Terms Systems should have a short circuit. Say in our content sharing system, if \u201cNotifications\u201d is not working, the site should gracefully handle that failure by removing the functionality instead of taking the whole site down. Swimlane is one of the commonly used fault isolation methodology. Swimlane adds a barrier to the service from other services so that failure on either of them won\u2019t affect the other. Say we roll out a new feature \u2018Advertisement\u2019 in our content sharing app. We can have two architectures If Ads are generated on the fly synchronously during each Newsfeed request, the faults in Ads feature gets propagated to Newsfeed feature. Instead if we swimlane \u201cGeneration of Ads\u201d service and use a shared storage to populate Newsfeed App, Ads failures won\u2019t cascade to Newsfeed and worst case if Ads don\u2019t meet SLA , we can have Newsfeed without Ads. Let's take another example, we come up with a new model for our Content sharing App. Here we roll out enterprise content sharing App where enterprises pay for the service and the content should never be shared outside the enterprise. Swimlane Principles Principle 1: Nothing is shared (also known as \u201cshare as little as possible\u201d). The less that is shared within a swim lane, the more fault isolative the swim lane becomes. (as shown in Enterprise usecase) Principle 2: Nothing crosses a swim lane boundary. Synchronous (defined by expecting a request\u2014not the transfer protocol) communication never crosses a swim lane boundary; if it does, the boundary is drawn incorrectly. (as shown in Ads feature) Swimlane Approaches Approach 1: Swim lane the money-maker. Never allow your cash register to be compromised by other systems. (Tier 1 vs Tier 2 in enterprise use case) Approach 2: Swim lane the biggest sources of incidents. Identify the recurring causes of pain and isolate them.(if Ads feature is in code yellow, swim laning it is the best option) Approach 3: Swim lane natural barriers. Customer boundaries make good swim lanes.(Public vs Enterprise customers) Refer https://learning.oreilly.com/library/view/the-art-of/9780134031408/ch21.html#ch21 SRE Use cases: Work with the DC tech or cloud team to distribute infrastructure such that its immune to switch or power failures by creating fault zones within a Data Center https://docs.microsoft.com/en-us/azure/virtual-machines/manage-availability#use-availability-zones-to-protect-from-datacenter-level-failures Work with the partners and design interaction between services such that one service breakdown is not amplified in a cascading fashion to all upstreams","title":"Fault Tolerance"},{"location":"systems_design/fault-tolerance/#fault-tolerance","text":"Failures are not avoidable in any system and will happen all the time, hence we need to build systems that can tolerate failures or recover from them. In systems, failure is the norm rather than the exception. \"Anything that can go wrong will go wrong\u201d -- Murphy\u2019s Law \u201cComplex systems contain changing mixtures of failures latent within them\u201d -- How Complex Systems Fail.","title":"Fault Tolerance"},{"location":"systems_design/fault-tolerance/#fault-tolerance-failure-metrics","text":"Common failure metrics that get measured and tracked for any system. Mean time to repair (MTTR): The average time to repair and restore a failed system. Mean time between failures (MTBF): The average operational time between one device failure or system breakdown and the next. Mean time to failure (MTTF): The average time a device or system is expected to function before it fails. Mean time to detect (MTTD): The average time between the onset of a problem and when the organization detects it. Mean time to investigate (MTTI): The average time between the detection of an incident and when the organization begins to investigate its cause and solution. Mean time to restore service (MTRS): The average elapsed time from the detection of an incident until the affected system or component is again available to users. Mean time between system incidents (MTBSI): The average elapsed time between the detection of two consecutive incidents. MTBSI can be calculated by adding MTBF and MTRS (MTBSI = MTBF + MTRS). Failure rate: Another reliability metric, which measures the frequency with which a component or system fails. It is expressed as a number of failures over a unit of time.","title":"Fault Tolerance - Failure Metrics"},{"location":"systems_design/fault-tolerance/#refer","text":"https://www.splunk.com/en_us/data-insider/what-is-mean-time-to-repair.html","title":"Refer"},{"location":"systems_design/fault-tolerance/#fault-tolerance-fault-isolation-terms","text":"Systems should have a short circuit. Say in our content sharing system, if \u201cNotifications\u201d is not working, the site should gracefully handle that failure by removing the functionality instead of taking the whole site down. Swimlane is one of the commonly used fault isolation methodology. Swimlane adds a barrier to the service from other services so that failure on either of them won\u2019t affect the other. Say we roll out a new feature \u2018Advertisement\u2019 in our content sharing app. We can have two architectures If Ads are generated on the fly synchronously during each Newsfeed request, the faults in Ads feature gets propagated to Newsfeed feature. Instead if we swimlane \u201cGeneration of Ads\u201d service and use a shared storage to populate Newsfeed App, Ads failures won\u2019t cascade to Newsfeed and worst case if Ads don\u2019t meet SLA , we can have Newsfeed without Ads. Let's take another example, we come up with a new model for our Content sharing App. Here we roll out enterprise content sharing App where enterprises pay for the service and the content should never be shared outside the enterprise.","title":"Fault Tolerance - Fault Isolation Terms"},{"location":"systems_design/fault-tolerance/#swimlane-principles","text":"Principle 1: Nothing is shared (also known as \u201cshare as little as possible\u201d). The less that is shared within a swim lane, the more fault isolative the swim lane becomes. (as shown in Enterprise usecase) Principle 2: Nothing crosses a swim lane boundary. Synchronous (defined by expecting a request\u2014not the transfer protocol) communication never crosses a swim lane boundary; if it does, the boundary is drawn incorrectly. (as shown in Ads feature)","title":"Swimlane Principles"},{"location":"systems_design/fault-tolerance/#swimlane-approaches","text":"Approach 1: Swim lane the money-maker. Never allow your cash register to be compromised by other systems. (Tier 1 vs Tier 2 in enterprise use case) Approach 2: Swim lane the biggest sources of incidents. Identify the recurring causes of pain and isolate them.(if Ads feature is in code yellow, swim laning it is the best option) Approach 3: Swim lane natural barriers. Customer boundaries make good swim lanes.(Public vs Enterprise customers)","title":"Swimlane Approaches"},{"location":"systems_design/fault-tolerance/#refer_1","text":"https://learning.oreilly.com/library/view/the-art-of/9780134031408/ch21.html#ch21","title":"Refer"},{"location":"systems_design/fault-tolerance/#sre-use-cases","text":"Work with the DC tech or cloud team to distribute infrastructure such that its immune to switch or power failures by creating fault zones within a Data Center https://docs.microsoft.com/en-us/azure/virtual-machines/manage-availability#use-availability-zones-to-protect-from-datacenter-level-failures Work with the partners and design interaction between services such that one service breakdown is not amplified in a cascading fashion to all upstreams","title":"SRE Use cases:"},{"location":"systems_design/intro/","text":"Systems Design Pre - Requisites Fundamentals of common software system components: - Operating Systems - Networking - Databases RDBMS/NoSQL What to expect from this training Thinking about and designing for scalability, availability, and reliability of large scale software systems. What is not covered under this training Individual software components\u2019 scalability and reliability concerns like e.g. Databases, while the same scalability principles and thinking can be applied, these individual components have their own specific nuances when scaling them and thinking about their reliability. More light will be shed on concepts rather than on setting up and configuring components like Loadbalancers to achieve scalability, availability and reliability of systems Training Content Introduction Scalability High Availability Fault Tolerance Introduction So, how do you go about learning to design a system? \u201d Like most great questions, it showed a level of naivety that was breathtaking. The only short answer I could give was, essentially, that you learned how to design a system by designing systems and finding out what works and what doesn\u2019t work.\u201d Jim Waldo, Sun Microsystems, On System Design As software and hardware systems have multiple moving parts, we need to think about how those parts will grow, their failure modes, their inter-dependencies, how it will impact the users and the business. There is no one-shot method or way to learn or do system design, we only learn to design systems by designing and iterating on them. This course will be a starter to make one think about scalability, availability, and fault tolerance during systems design. Backstory Let\u2019s design a simple content sharing application where users can share photos, media in our application which can be liked by their friends. Let\u2019s start with a simple design of the application and evolve it as we learn system design concepts","title":"Intro"},{"location":"systems_design/intro/#systems-design","text":"","title":"Systems Design"},{"location":"systems_design/intro/#pre-requisites","text":"Fundamentals of common software system components: - Operating Systems - Networking - Databases RDBMS/NoSQL","title":"Pre - Requisites"},{"location":"systems_design/intro/#what-to-expect-from-this-training","text":"Thinking about and designing for scalability, availability, and reliability of large scale software systems.","title":"What to expect from this training"},{"location":"systems_design/intro/#what-is-not-covered-under-this-training","text":"Individual software components\u2019 scalability and reliability concerns like e.g. Databases, while the same scalability principles and thinking can be applied, these individual components have their own specific nuances when scaling them and thinking about their reliability. More light will be shed on concepts rather than on setting up and configuring components like Loadbalancers to achieve scalability, availability and reliability of systems","title":"What is not covered under this training"},{"location":"systems_design/intro/#training-content","text":"Introduction Scalability High Availability Fault Tolerance","title":"Training Content"},{"location":"systems_design/intro/#introduction","text":"So, how do you go about learning to design a system? \u201d Like most great questions, it showed a level of naivety that was breathtaking. The only short answer I could give was, essentially, that you learned how to design a system by designing systems and finding out what works and what doesn\u2019t work.\u201d Jim Waldo, Sun Microsystems, On System Design As software and hardware systems have multiple moving parts, we need to think about how those parts will grow, their failure modes, their inter-dependencies, how it will impact the users and the business. There is no one-shot method or way to learn or do system design, we only learn to design systems by designing and iterating on them. This course will be a starter to make one think about scalability, availability, and fault tolerance during systems design.","title":"Introduction"},{"location":"systems_design/intro/#backstory","text":"Let\u2019s design a simple content sharing application where users can share photos, media in our application which can be liked by their friends. Let\u2019s start with a simple design of the application and evolve it as we learn system design concepts","title":"Backstory"},{"location":"systems_design/scalability/","text":"Scalability What does scalability mean for a system/service? A system is composed of services/components, each service/component scalability needs to be tackled separately, and the scalability of the system as a whole. A service is said to be scalable if, as resources are added to the system, it results in increased performance in a manner proportional to resources added An always-on service is said to be scalable if adding resources to facilitate redundancy does not result in a loss of performance Refer https://www.allthingsdistributed.com/2006/03/a_word_on_scalability.html Scalability - AKF Scale Cube The Scale Cube is a model for segmenting services, defining microservices, and scaling products. It also creates a common language for teams to discuss scale related options in designing solutions. Following section talks about certain scaling patterns based on our inferences from AKF cube Scalability - Horizontal scaling Horizontal scaling stands for cloning of an application or service such that work can easily be distributed across instances with absolutely no bias. Lets see how our monolithic application improves with this principle Here DB is scaled separately from the application. This is to let you know each component\u2019s scaling capabilities can be different. Usually web applications can be scaled by adding resources unless there is no state stored inside the application. But DBs can be scaled only for Reads by adding more followers but Writes have to go to only one master to make sure data is consistent. There are some DBs which support multi master writes but we are keeping them out of scope at this point. Apps should be able to differentiate between Read and Writes to choose appropriate DB servers. Load balancers can split traffic between identical servers transparently. WHAT: Duplication of services or databases to spread transaction load. WHEN TO USE: Databases with a very high read-to-write ratio (5:1 or greater\u2014the higher the better). Because only read replicas of DBs can be scaled, not the Master. HOW TO USE: Simply clone services and implement a load balancer. For databases, ensure that the accessing code understands the difference between a read and a write. WHY: Allows for fast scale of transactions at the cost of duplicated data and functionality. KEY TAKEAWAYS: This is fast to implement, is low cost from a developer effort perspective, and can scale transaction volumes nicely. However, they tend to be high cost from the perspective of the operational cost of data. Cost here means if we have 3 followers and 1 Master DB, the same database will be stored as 4 copies in the 4 servers. Hence added storage cost Refer https://learning.oreilly.com/library/view/the-art-of/9780134031408/ch23.html Scalability Pattern - Load Balancing Improves the distribution of workloads across multiple computing resources, such as computers, a computer cluster, network links, central processing units, or disk drives. Commonly used technique is load balancing traffic across identical server clusters. Similar philosophy is used to load balance traffic across network links by ECMP , disk drives by RAID etc Aims to optimize resource use, maximize throughput, minimize response time, and avoid overload of any single resource. Using multiple components with load balancing instead of a single component may increase reliability and availability through redundancy. In our updated architecture diagram we have 4 servers to handle app traffic instead of a single server The device or system that performs load balancing is called a load balancer, abbreviated as LB. Refer https://en.wikipedia.org/wiki/Load_balancing_(computing) https://blog.envoyproxy.io/introduction-to-modern-network-load-balancing-and-proxying-a57f6ff80236 https://learning.oreilly.com/library/view/load-balancing-in/9781492038009/ https://learning.oreilly.com/library/view/practical-load-balancing/9781430236801/ http://shop.oreilly.com/product/9780596000509.do Scalability Pattern - LB Tasks What does an LB do? Service discovery: What backends are available in the system? In our architecture, 4 servers are available to serve App traffic. LB acts as a single endpoint that clients can use transparently to reach one of the 4 servers. Health checking: What backends are currently healthy and available to accept requests? If one out of the 4 App servers turns bad, LB should automatically short circuit the path so that clients don\u2019t sense any application downtime Load balancing: What algorithm should be used to balance individual requests across the healthy backends? There are many algorithms to distribute traffic across one of the four servers. Based on observations/experience, SRE can pick the algorithm that suits their pattern Scalability Pattern - LB Methods Common Load Balancing Methods Least Connection Method directs traffic to the server with the fewest active connections. Most useful when there are a large number of persistent connections in the traffic unevenly distributed between the servers. Works if clients maintain long lived connections Least Response Time Method directs traffic to the server with the fewest active connections and the lowest average response time. Here response time is used to provide feedback of server\u2019s health Round Robin Method rotates servers by directing traffic to the first available server and then moves that server to the bottom of the queue. Most useful when servers are of equal specification and there are not many persistent connections. IP Hash the IP address of the client determines which server receives the request. This can sometimes cause skewness in distribution but is useful if apps store some state locally and need some stickiness More advanced client/server-side example techniques - https://docs.nginx.com/nginx/admin-guide/load-balancer/ - http://cbonte.github.io/haproxy-dconv/2.2/intro.html#3.3.5 - https://twitter.github.io/finagle/guide/Clients.html#load-balancing Scalability Pattern - Caching - Content Delivery Networks (CDN) CDNs are added closer to the client\u2019s location. If the app has static data like images, Javascript, CSS which don\u2019t change very often, they can be cached. Since our example is a content sharing site, static content can be cached in CDNs with a suitable expiry. WHAT: Use CDNs (content delivery networks) to offload traffic from your site. WHEN TO USE: When speed improvements and scale warrant the additional cost. HOW TO USE: Most CDNs leverage DNS to serve content on your site\u2019s behalf. Thus you may need to make minor DNS changes or additions and move content to be served from new subdomains. Eg media-exp1.licdn.com is a domain used by Linkedin to serve static content Here a CNAME points the domain to the DNS of CDN provider dig media-exp1.licdn.com +short 2-01-2c3e-005c.cdx.cedexis.net. WHY: CDNs help offload traffic spikes and are often economical ways to scale parts of a site\u2019s traffic. They also often substantially improve page download times. KEY TAKEAWAYS: CDNs are a fast and simple way to offset the spikiness of traffic as well as traffic growth in general. Make sure you perform a cost-benefit analysis and monitor the CDN usage. If CDNs have a lot of cache misses, then we don\u2019t gain much from CDN and are still serving requests using our compute resources. Scalability - Microservices This pattern represents the separation of work by service or function within the application. Microservices are meant to address the issues associated with growth and complexity in the code base and data sets. The intent is to create fault isolation as well as to reduce response times. Microservices can scale transactions, data sizes, and codebase sizes. They are most effective in scaling the size and complexity of your codebase. They tend to cost a bit more than horizontal scaling because the engineering team needs to rewrite services or, at the very least, disaggregate them from the original monolithic application. WHAT: Sometimes referred to as scale through services or resources, this rule focuses on scaling by splitting data sets, transactions, and engineering teams along verb (services) or noun (resources) boundaries. WHEN TO USE: Very large data sets where relations between data are not necessary. Large, complex systems where scaling engineering resources requires specialization. HOW TO USE: Split up actions by using verbs, or resources by using nouns, or use a mix. Split both the services and the data along the lines defined by the verb/noun approach. WHY: Allows for efficient scaling of not only transactions but also very large data sets associated with those transactions. It also allows for the efficient scaling of teams. KEY TAKEAWAYS: Microservices allow for efficient scaling of transactions, large data sets, and can help with fault isolation. It helps reduce the communication overhead of teams. The codebase becomes less complex as disjoint features are decoupled and spun as new services thereby letting each service scale independently specific to its requirement. Refer https://learning.oreilly.com/library/view/the-art-of/9780134031408/ch23.html Scalability - Sharding This pattern represents the separation of work based on attributes that are looked up or determined at the time of the transaction. Most often, these are implemented as splits by requestor, customer, or client. Very often, a lookup service or deterministic algorithm will need to be written for these types of splits. Sharding aids in scaling transaction growth, scaling instruction sets, and decreasing processing time (the last by limiting the data necessary to perform any transaction). This is more effective at scaling growth in customers or clients. It can aid with disaster recovery efforts, and limit the impact of incidents to only a specific segment of customers. Here the auth data is sharded based on user names so that DBs can respond faster as the amount of data DBs have to work on has drastically reduced during queries. There can be other ways to split Here the whole data centre is split and replicated and clients are directed to a data centre based on their geography. This helps in improving performance as clients are directed to the closest Data centre and performance increases as we add more data centres. There are some replication and consistency overhead with this approach one needs to be aware of. This also gives fault tolerance by rolling out test features to one site and rollback if there is an impact to that geography WHAT: This is very often a split by some unique aspect of the customer such as customer ID, name, geography, and so on. WHEN TO USE: Very large, similar data sets such as large and rapidly growing customer bases or when the response time for a geographically distributed customer base is important. HOW TO USE: Identify something you know about the customer, such as customer ID, last name, geography, or device, and split or partition both data and services based on that attribute. WHY: Rapid customer growth exceeds other forms of data growth, or you have the need to perform fault isolation between certain customer groups as you scale. KEY TAKEAWAYS: Shards are effective at helping you to scale customer bases but can also be applied to other very large data sets that can\u2019t be pulled apart using the microservices methodology. Refer https://learning.oreilly.com/library/view/the-art-of/9780134031408/ch23.html SRE Use cases SREs in coordination with the network team work on how to map users traffic to a particular site. https://engineering.linkedin.com/blog/2017/05/trafficshift--load-testing-at-scale SREs work closely with the Dev team to split monoliths to multiple microservices that are easy to run and manage SREs work on improving Load Balancers' reliability, service discovery and performance SREs work closely to split Data into shards and manage data integrity and consistency. https://engineering.linkedin.com/espresso/introducing-espresso-linkedins-hot-new-distributed-document-store SREs work to set up, configure and improve CDN cache hit rate.","title":"Scalability"},{"location":"systems_design/scalability/#scalability","text":"What does scalability mean for a system/service? A system is composed of services/components, each service/component scalability needs to be tackled separately, and the scalability of the system as a whole. A service is said to be scalable if, as resources are added to the system, it results in increased performance in a manner proportional to resources added An always-on service is said to be scalable if adding resources to facilitate redundancy does not result in a loss of performance","title":"Scalability"},{"location":"systems_design/scalability/#refer","text":"https://www.allthingsdistributed.com/2006/03/a_word_on_scalability.html","title":"Refer"},{"location":"systems_design/scalability/#scalability-akf-scale-cube","text":"The Scale Cube is a model for segmenting services, defining microservices, and scaling products. It also creates a common language for teams to discuss scale related options in designing solutions. Following section talks about certain scaling patterns based on our inferences from AKF cube","title":"Scalability - AKF Scale Cube"},{"location":"systems_design/scalability/#scalability-horizontal-scaling","text":"Horizontal scaling stands for cloning of an application or service such that work can easily be distributed across instances with absolutely no bias. Lets see how our monolithic application improves with this principle Here DB is scaled separately from the application. This is to let you know each component\u2019s scaling capabilities can be different. Usually web applications can be scaled by adding resources unless there is no state stored inside the application. But DBs can be scaled only for Reads by adding more followers but Writes have to go to only one master to make sure data is consistent. There are some DBs which support multi master writes but we are keeping them out of scope at this point. Apps should be able to differentiate between Read and Writes to choose appropriate DB servers. Load balancers can split traffic between identical servers transparently. WHAT: Duplication of services or databases to spread transaction load. WHEN TO USE: Databases with a very high read-to-write ratio (5:1 or greater\u2014the higher the better). Because only read replicas of DBs can be scaled, not the Master. HOW TO USE: Simply clone services and implement a load balancer. For databases, ensure that the accessing code understands the difference between a read and a write. WHY: Allows for fast scale of transactions at the cost of duplicated data and functionality. KEY TAKEAWAYS: This is fast to implement, is low cost from a developer effort perspective, and can scale transaction volumes nicely. However, they tend to be high cost from the perspective of the operational cost of data. Cost here means if we have 3 followers and 1 Master DB, the same database will be stored as 4 copies in the 4 servers. Hence added storage cost","title":"Scalability - Horizontal scaling"},{"location":"systems_design/scalability/#refer_1","text":"https://learning.oreilly.com/library/view/the-art-of/9780134031408/ch23.html","title":"Refer"},{"location":"systems_design/scalability/#scalability-pattern-load-balancing","text":"Improves the distribution of workloads across multiple computing resources, such as computers, a computer cluster, network links, central processing units, or disk drives. Commonly used technique is load balancing traffic across identical server clusters. Similar philosophy is used to load balance traffic across network links by ECMP , disk drives by RAID etc Aims to optimize resource use, maximize throughput, minimize response time, and avoid overload of any single resource. Using multiple components with load balancing instead of a single component may increase reliability and availability through redundancy. In our updated architecture diagram we have 4 servers to handle app traffic instead of a single server The device or system that performs load balancing is called a load balancer, abbreviated as LB.","title":"Scalability Pattern - Load Balancing"},{"location":"systems_design/scalability/#refer_2","text":"https://en.wikipedia.org/wiki/Load_balancing_(computing) https://blog.envoyproxy.io/introduction-to-modern-network-load-balancing-and-proxying-a57f6ff80236 https://learning.oreilly.com/library/view/load-balancing-in/9781492038009/ https://learning.oreilly.com/library/view/practical-load-balancing/9781430236801/ http://shop.oreilly.com/product/9780596000509.do","title":"Refer"},{"location":"systems_design/scalability/#scalability-pattern-lb-tasks","text":"What does an LB do?","title":"Scalability Pattern - LB Tasks"},{"location":"systems_design/scalability/#service-discovery","text":"What backends are available in the system? In our architecture, 4 servers are available to serve App traffic. LB acts as a single endpoint that clients can use transparently to reach one of the 4 servers.","title":"Service discovery:"},{"location":"systems_design/scalability/#health-checking","text":"What backends are currently healthy and available to accept requests? If one out of the 4 App servers turns bad, LB should automatically short circuit the path so that clients don\u2019t sense any application downtime","title":"Health checking:"},{"location":"systems_design/scalability/#load-balancing","text":"What algorithm should be used to balance individual requests across the healthy backends? There are many algorithms to distribute traffic across one of the four servers. Based on observations/experience, SRE can pick the algorithm that suits their pattern","title":"Load balancing:"},{"location":"systems_design/scalability/#scalability-pattern-lb-methods","text":"Common Load Balancing Methods","title":"Scalability Pattern - LB Methods"},{"location":"systems_design/scalability/#least-connection-method","text":"directs traffic to the server with the fewest active connections. Most useful when there are a large number of persistent connections in the traffic unevenly distributed between the servers. Works if clients maintain long lived connections","title":"Least Connection Method"},{"location":"systems_design/scalability/#least-response-time-method","text":"directs traffic to the server with the fewest active connections and the lowest average response time. Here response time is used to provide feedback of server\u2019s health","title":"Least Response Time Method"},{"location":"systems_design/scalability/#round-robin-method","text":"rotates servers by directing traffic to the first available server and then moves that server to the bottom of the queue. Most useful when servers are of equal specification and there are not many persistent connections.","title":"Round Robin Method"},{"location":"systems_design/scalability/#ip-hash","text":"the IP address of the client determines which server receives the request. This can sometimes cause skewness in distribution but is useful if apps store some state locally and need some stickiness More advanced client/server-side example techniques - https://docs.nginx.com/nginx/admin-guide/load-balancer/ - http://cbonte.github.io/haproxy-dconv/2.2/intro.html#3.3.5 - https://twitter.github.io/finagle/guide/Clients.html#load-balancing","title":"IP Hash"},{"location":"systems_design/scalability/#scalability-pattern-caching-content-delivery-networks-cdn","text":"CDNs are added closer to the client\u2019s location. If the app has static data like images, Javascript, CSS which don\u2019t change very often, they can be cached. Since our example is a content sharing site, static content can be cached in CDNs with a suitable expiry. WHAT: Use CDNs (content delivery networks) to offload traffic from your site. WHEN TO USE: When speed improvements and scale warrant the additional cost. HOW TO USE: Most CDNs leverage DNS to serve content on your site\u2019s behalf. Thus you may need to make minor DNS changes or additions and move content to be served from new subdomains. Eg media-exp1.licdn.com is a domain used by Linkedin to serve static content Here a CNAME points the domain to the DNS of CDN provider dig media-exp1.licdn.com +short 2-01-2c3e-005c.cdx.cedexis.net. WHY: CDNs help offload traffic spikes and are often economical ways to scale parts of a site\u2019s traffic. They also often substantially improve page download times. KEY TAKEAWAYS: CDNs are a fast and simple way to offset the spikiness of traffic as well as traffic growth in general. Make sure you perform a cost-benefit analysis and monitor the CDN usage. If CDNs have a lot of cache misses, then we don\u2019t gain much from CDN and are still serving requests using our compute resources.","title":"Scalability Pattern - Caching - Content Delivery Networks (CDN)"},{"location":"systems_design/scalability/#scalability-microservices","text":"This pattern represents the separation of work by service or function within the application. Microservices are meant to address the issues associated with growth and complexity in the code base and data sets. The intent is to create fault isolation as well as to reduce response times. Microservices can scale transactions, data sizes, and codebase sizes. They are most effective in scaling the size and complexity of your codebase. They tend to cost a bit more than horizontal scaling because the engineering team needs to rewrite services or, at the very least, disaggregate them from the original monolithic application. WHAT: Sometimes referred to as scale through services or resources, this rule focuses on scaling by splitting data sets, transactions, and engineering teams along verb (services) or noun (resources) boundaries. WHEN TO USE: Very large data sets where relations between data are not necessary. Large, complex systems where scaling engineering resources requires specialization. HOW TO USE: Split up actions by using verbs, or resources by using nouns, or use a mix. Split both the services and the data along the lines defined by the verb/noun approach. WHY: Allows for efficient scaling of not only transactions but also very large data sets associated with those transactions. It also allows for the efficient scaling of teams. KEY TAKEAWAYS: Microservices allow for efficient scaling of transactions, large data sets, and can help with fault isolation. It helps reduce the communication overhead of teams. The codebase becomes less complex as disjoint features are decoupled and spun as new services thereby letting each service scale independently specific to its requirement.","title":"Scalability - Microservices"},{"location":"systems_design/scalability/#refer_3","text":"https://learning.oreilly.com/library/view/the-art-of/9780134031408/ch23.html","title":"Refer"},{"location":"systems_design/scalability/#scalability-sharding","text":"This pattern represents the separation of work based on attributes that are looked up or determined at the time of the transaction. Most often, these are implemented as splits by requestor, customer, or client. Very often, a lookup service or deterministic algorithm will need to be written for these types of splits. Sharding aids in scaling transaction growth, scaling instruction sets, and decreasing processing time (the last by limiting the data necessary to perform any transaction). This is more effective at scaling growth in customers or clients. It can aid with disaster recovery efforts, and limit the impact of incidents to only a specific segment of customers. Here the auth data is sharded based on user names so that DBs can respond faster as the amount of data DBs have to work on has drastically reduced during queries. There can be other ways to split Here the whole data centre is split and replicated and clients are directed to a data centre based on their geography. This helps in improving performance as clients are directed to the closest Data centre and performance increases as we add more data centres. There are some replication and consistency overhead with this approach one needs to be aware of. This also gives fault tolerance by rolling out test features to one site and rollback if there is an impact to that geography WHAT: This is very often a split by some unique aspect of the customer such as customer ID, name, geography, and so on. WHEN TO USE: Very large, similar data sets such as large and rapidly growing customer bases or when the response time for a geographically distributed customer base is important. HOW TO USE: Identify something you know about the customer, such as customer ID, last name, geography, or device, and split or partition both data and services based on that attribute. WHY: Rapid customer growth exceeds other forms of data growth, or you have the need to perform fault isolation between certain customer groups as you scale. KEY TAKEAWAYS: Shards are effective at helping you to scale customer bases but can also be applied to other very large data sets that can\u2019t be pulled apart using the microservices methodology.","title":"Scalability - Sharding"},{"location":"systems_design/scalability/#refer_4","text":"https://learning.oreilly.com/library/view/the-art-of/9780134031408/ch23.html","title":"Refer"},{"location":"systems_design/scalability/#sre-use-cases","text":"SREs in coordination with the network team work on how to map users traffic to a particular site. https://engineering.linkedin.com/blog/2017/05/trafficshift--load-testing-at-scale SREs work closely with the Dev team to split monoliths to multiple microservices that are easy to run and manage SREs work on improving Load Balancers' reliability, service discovery and performance SREs work closely to split Data into shards and manage data integrity and consistency. https://engineering.linkedin.com/espresso/introducing-espresso-linkedins-hot-new-distributed-document-store SREs work to set up, configure and improve CDN cache hit rate.","title":"SRE Use cases"}]} \ No newline at end of file +{"config":{"lang":["en"],"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"Hello, World!!!","title":"Home"},{"location":"big_data/architecture/","text":"Architecture of Hadoop HDFS The Hadoop Distributed File System (HDFS) is a distributed file system designed to run on commodity hardware. It has many similarities with existing distributed file systems. However, the differences from other distributed file systems are significant. HDFS is highly fault-tolerant and is designed to be deployed on low-cost hardware. HDFS provides high throughput access to application data and is suitable for applications that have large data sets. HDFS is part of the Apache Hadoop Core project. 1. NameNode: is the arbitrator and central repository of file namespace in the cluster. The NameNode executes the operations such as opening, closing, and renaming files and directories. 2. DataNode: manages the storage attached to the node on which it runs. It is responsible for serving all the read and write requests. It performs operations on instructions on NameNode such as creation, deletion, and replications of blocks. 3. Client: Responsible for getting the required metadata from the namenode and then communicating with the datanodes for reads and writes. YARN YARN stands for \u201cYet Another Resource Negotiator\u201c. It was introduced in Hadoop 2.0 to remove the bottleneck on Job Tracker which was present in Hadoop 1.0. YARN was described as a \u201cRedesigned Resource Manager\u201d at the time of its launching, but it has now evolved to be known as a large-scale distributed operating system used for Big Data processing. The main components of YARN architecture include: 1. Client: It submits map-reduce jobs to the resource manager. 2. Resource Manager: It is the master daemon of YARN and is responsible for resource assignment and management among all the applications. Whenever it receives a processing request, it forwards it to the corresponding node manager and allocates resources for the completion of the request accordingly. It has two major components: 3. Scheduler: It performs scheduling based on the allocated application and available resources. It is a pure scheduler, which means that it does not perform other tasks such as monitoring or tracking and does not guarantee a restart if a task fails. The YARN scheduler supports plugins such as Capacity Scheduler and Fair Scheduler to partition the cluster resources. 4. Application manager: It is responsible for accepting the application and negotiating the first container from the resource manager. It also restarts the Application Manager container if a task fails. 5. Node Manager: It takes care of individual nodes on the Hadoop cluster and manages application and workflow and that particular node. Its primary job is to keep-up with the Node Manager. It monitors resource usage, performs log management and also kills a container based on directions from the resource manager. It is also responsible for creating the container process and starting it on the request of the Application master. 6. Application Master: An application is a single job submitted to a framework. The application manager is responsible for negotiating resources with the resource manager, tracking the status and monitoring progress of a single application. The application master requests the container from the node manager by sending a Container Launch Context(CLC) which includes everything an application needs to run. Once the application is started, it sends the health report to the resource manager from time-to-time. 7. Container: It is a collection of physical resources such as RAM, CPU cores and disk on a single node. The containers are invoked by Container Launch Context(CLC) which is a record that contains information such as environment variables, security tokens, dependencies etc. MapReduce framework 1. The term MapReduce represents two separate and distinct tasks Hadoop programs perform-Map Job and Reduce Job. Map jobs take data sets as input and process them to produce key value pairs. Reduce job takes the output of the Map job i.e. the key value pairs and aggregates them to produce desired results. 2. Hadoop MapReduce (Hadoop Map/Reduce) is a software framework for distributed processing of large data sets on computing clusters. Mapreduce helps to split the input data set into a number of parts and run a program on all data parts parallel at once. 3. Please find the below Word count example demonstrating the usage of MapReduce framework: Other tooling around hadoop Hive Uses a language called HQL which is very SQL like. Gives non-programmers the ability to query and analyze data in Hadoop. Is basically an abstraction layer on top of map-reduce. Ex. HQL query: SELECT pet.name, comment FROM pet JOIN event ON (pet.name = event.name); In mysql: SELECT pet.name, comment FROM pet, event WHERE pet.name = event.name; Pig Uses a scripting language called Pig Latin, which is more workflow driven. Don't need to be an expert Java programmer but need a few coding skills. Is also an abstraction layer on top of map-reduce. Here is a quick question for you: What is the output of running the pig queries in the right column against the data present in the left column in the below image? Output: mysql 7,Komal,Nayak,24,9848022334,trivendram 8,Bharathi,Nambiayar,24,9848022333,Chennai 5,Trupthi,Mohanthy,23,9848022336,Bhuwaneshwar 6,Archana,Mishra,23,9848022335,Chennai 3. Spark 1. Spark provides primitives for in-memory cluster computing that allows user programs to load data into a cluster\u2019s memory and query it repeatedly, making it well suited to machine learning algorithms. 4. Presto 1. Presto is a high performance, distributed SQL query engine for Big Data. 2. Its architecture allows users to query a variety of data sources such as Hadoop, AWS S3, Alluxio, MySQL, Cassandra, Kafka, and MongoDB. 3. Example presto query: mysql use studentDB; show tables; SELECT roll_no, name FROM studentDB.studentDetails where section=\u2019A\u2019 limit 5; Data Serialisation and storage In order to transport the data over the network or to store on some persistent storage, we use the process of translating data structures or objects state into binary or textual form. We call this process serialization.. Avro data is stored in a container file (a .avro file) and its schema (the .avsc file) is stored with the data file. Apache Hive provides support to store a table as Avro and can also query data in this serialisation format.","title":"Architecture of Hadoop"},{"location":"big_data/architecture/#architecture-of-hadoop","text":"HDFS The Hadoop Distributed File System (HDFS) is a distributed file system designed to run on commodity hardware. It has many similarities with existing distributed file systems. However, the differences from other distributed file systems are significant. HDFS is highly fault-tolerant and is designed to be deployed on low-cost hardware. HDFS provides high throughput access to application data and is suitable for applications that have large data sets. HDFS is part of the Apache Hadoop Core project. 1. NameNode: is the arbitrator and central repository of file namespace in the cluster. The NameNode executes the operations such as opening, closing, and renaming files and directories. 2. DataNode: manages the storage attached to the node on which it runs. It is responsible for serving all the read and write requests. It performs operations on instructions on NameNode such as creation, deletion, and replications of blocks. 3. Client: Responsible for getting the required metadata from the namenode and then communicating with the datanodes for reads and writes. YARN YARN stands for \u201cYet Another Resource Negotiator\u201c. It was introduced in Hadoop 2.0 to remove the bottleneck on Job Tracker which was present in Hadoop 1.0. YARN was described as a \u201cRedesigned Resource Manager\u201d at the time of its launching, but it has now evolved to be known as a large-scale distributed operating system used for Big Data processing. The main components of YARN architecture include: 1. Client: It submits map-reduce jobs to the resource manager. 2. Resource Manager: It is the master daemon of YARN and is responsible for resource assignment and management among all the applications. Whenever it receives a processing request, it forwards it to the corresponding node manager and allocates resources for the completion of the request accordingly. It has two major components: 3. Scheduler: It performs scheduling based on the allocated application and available resources. It is a pure scheduler, which means that it does not perform other tasks such as monitoring or tracking and does not guarantee a restart if a task fails. The YARN scheduler supports plugins such as Capacity Scheduler and Fair Scheduler to partition the cluster resources. 4. Application manager: It is responsible for accepting the application and negotiating the first container from the resource manager. It also restarts the Application Manager container if a task fails. 5. Node Manager: It takes care of individual nodes on the Hadoop cluster and manages application and workflow and that particular node. Its primary job is to keep-up with the Node Manager. It monitors resource usage, performs log management and also kills a container based on directions from the resource manager. It is also responsible for creating the container process and starting it on the request of the Application master. 6. Application Master: An application is a single job submitted to a framework. The application manager is responsible for negotiating resources with the resource manager, tracking the status and monitoring progress of a single application. The application master requests the container from the node manager by sending a Container Launch Context(CLC) which includes everything an application needs to run. Once the application is started, it sends the health report to the resource manager from time-to-time. 7. Container: It is a collection of physical resources such as RAM, CPU cores and disk on a single node. The containers are invoked by Container Launch Context(CLC) which is a record that contains information such as environment variables, security tokens, dependencies etc.","title":"Architecture of Hadoop"},{"location":"big_data/architecture/#mapreduce-framework","text":"1. The term MapReduce represents two separate and distinct tasks Hadoop programs perform-Map Job and Reduce Job. Map jobs take data sets as input and process them to produce key value pairs. Reduce job takes the output of the Map job i.e. the key value pairs and aggregates them to produce desired results. 2. Hadoop MapReduce (Hadoop Map/Reduce) is a software framework for distributed processing of large data sets on computing clusters. Mapreduce helps to split the input data set into a number of parts and run a program on all data parts parallel at once. 3. Please find the below Word count example demonstrating the usage of MapReduce framework:","title":"MapReduce framework"},{"location":"big_data/architecture/#other-tooling-around-hadoop","text":"Hive Uses a language called HQL which is very SQL like. Gives non-programmers the ability to query and analyze data in Hadoop. Is basically an abstraction layer on top of map-reduce. Ex. HQL query: SELECT pet.name, comment FROM pet JOIN event ON (pet.name = event.name); In mysql: SELECT pet.name, comment FROM pet, event WHERE pet.name = event.name; Pig Uses a scripting language called Pig Latin, which is more workflow driven. Don't need to be an expert Java programmer but need a few coding skills. Is also an abstraction layer on top of map-reduce. Here is a quick question for you: What is the output of running the pig queries in the right column against the data present in the left column in the below image? Output: mysql 7,Komal,Nayak,24,9848022334,trivendram 8,Bharathi,Nambiayar,24,9848022333,Chennai 5,Trupthi,Mohanthy,23,9848022336,Bhuwaneshwar 6,Archana,Mishra,23,9848022335,Chennai 3. Spark 1. Spark provides primitives for in-memory cluster computing that allows user programs to load data into a cluster\u2019s memory and query it repeatedly, making it well suited to machine learning algorithms. 4. Presto 1. Presto is a high performance, distributed SQL query engine for Big Data. 2. Its architecture allows users to query a variety of data sources such as Hadoop, AWS S3, Alluxio, MySQL, Cassandra, Kafka, and MongoDB. 3. Example presto query: mysql use studentDB; show tables; SELECT roll_no, name FROM studentDB.studentDetails where section=\u2019A\u2019 limit 5;","title":"Other tooling around hadoop"},{"location":"big_data/architecture/#data-serialisation-and-storage","text":"In order to transport the data over the network or to store on some persistent storage, we use the process of translating data structures or objects state into binary or textual form. We call this process serialization.. Avro data is stored in a container file (a .avro file) and its schema (the .avsc file) is stored with the data file. Apache Hive provides support to store a table as Avro and can also query data in this serialisation format.","title":"Data Serialisation and storage"},{"location":"big_data/evolution/","text":"Evolution of Hadoop","title":"Evolution of Hadoop"},{"location":"big_data/evolution/#evolution-of-hadoop","text":"","title":"Evolution of Hadoop"},{"location":"big_data/intro/","text":"School of SRE: Big Data Pre - Reads Basics of Linux File systems. Basic understanding of System Design. Target Audience The concept of Big Data has been around for years; most organizations now understand that if they capture all the data that streams into their businesses, they can apply analytics and get significant value from it. This training material covers the basics of Big Data(using Hadoop) for beginners, who would like to quickly get started and get their hands dirty in this domain. What to expect from this training This course covers the basics of Big Data and how it has evolved to become what it is today. We will take a look at a few realistic scenarios where Big Data would be a perfect fit. An interesting assignment on designing a Big Data system is followed by understanding the architecture of Hadoop and the tooling around it. What is not covered under this training Writing programs to draw analytics from data. TOC: Overview of Big Data Usage of Big Data techniques Evolution of Hadoop Architecture of hadoop HDFS Yarn MapReduce framework Other tooling around hadoop Hive Pig Spark Presto Data Serialisation and storage","title":"Intro"},{"location":"big_data/intro/#school-of-sre-big-data","text":"","title":"School of SRE: Big Data"},{"location":"big_data/intro/#pre-reads","text":"Basics of Linux File systems. Basic understanding of System Design.","title":"Pre - Reads"},{"location":"big_data/intro/#target-audience","text":"The concept of Big Data has been around for years; most organizations now understand that if they capture all the data that streams into their businesses, they can apply analytics and get significant value from it. This training material covers the basics of Big Data(using Hadoop) for beginners, who would like to quickly get started and get their hands dirty in this domain.","title":"Target Audience"},{"location":"big_data/intro/#what-to-expect-from-this-training","text":"This course covers the basics of Big Data and how it has evolved to become what it is today. We will take a look at a few realistic scenarios where Big Data would be a perfect fit. An interesting assignment on designing a Big Data system is followed by understanding the architecture of Hadoop and the tooling around it.","title":"What to expect from this training"},{"location":"big_data/intro/#what-is-not-covered-under-this-training","text":"Writing programs to draw analytics from data.","title":"What is not covered under this training"},{"location":"big_data/intro/#toc","text":"Overview of Big Data Usage of Big Data techniques Evolution of Hadoop Architecture of hadoop HDFS Yarn MapReduce framework Other tooling around hadoop Hive Pig Spark Presto Data Serialisation and storage","title":"TOC:"},{"location":"big_data/overview/","text":"Overview of Big Data Big Data is a collection of large datasets that cannot be processed using traditional computing techniques. It is not a single technique or a tool, rather it has become a complete subject, which involves various tools, techniques and frameworks. Big Data could consist of Structured data Unstructured data Semi-structured data Characteristics of Big Data: Volume Variety Velocity Variability Examples of Big Data generation include stock exchanges, social media sites, jet engines, etc.","title":"Overview of Big Data"},{"location":"big_data/overview/#overview-of-big-data","text":"Big Data is a collection of large datasets that cannot be processed using traditional computing techniques. It is not a single technique or a tool, rather it has become a complete subject, which involves various tools, techniques and frameworks. Big Data could consist of Structured data Unstructured data Semi-structured data Characteristics of Big Data: Volume Variety Velocity Variability Examples of Big Data generation include stock exchanges, social media sites, jet engines, etc.","title":"Overview of Big Data"},{"location":"big_data/tasks/","text":"Tasks and conclusion Post training tasks: Try setting up your own 3 node hadoop cluster. A VM based solution can be found here Write a simple spark/MR job of your choice and understand how to generate analytics from data. Sample dataset can be found here References: Hadoop documentation HDFS Architecture YARN Architecture Google GFS paper","title":"Tasks and conclusion"},{"location":"big_data/tasks/#tasks-and-conclusion","text":"","title":"Tasks and conclusion"},{"location":"big_data/tasks/#post-training-tasks","text":"Try setting up your own 3 node hadoop cluster. A VM based solution can be found here Write a simple spark/MR job of your choice and understand how to generate analytics from data. Sample dataset can be found here","title":"Post training tasks:"},{"location":"big_data/tasks/#references","text":"Hadoop documentation HDFS Architecture YARN Architecture Google GFS paper","title":"References:"},{"location":"big_data/usage/","text":"Usage of Big Data techniques Take the example of the traffic lights problem. There are more than 300,000 traffic lights in the US as of 2018. Let us assume that we placed a device on each of them to collect metrics and send it to a central metrics collection system. If each of the IOT devices sends 10 events per minute, we have 300000x10x60x24 = 432x10^7 events per day. How would you go about processing that and telling me how many of the signals were \u201cgreen\u201d at 10:45 am on a particular day? Consider the next example on Unified Payments Interface (UPI) transactions: We had about 1.15 billion UPI transactions in the month of October, 2019 in India. If we try to extrapolate this data to about a year and try to find out some common payments that were happening through a particular UPI ID, how do you suggest we go about that?","title":"Usage of Big Data techniques"},{"location":"big_data/usage/#usage-of-big-data-techniques","text":"Take the example of the traffic lights problem. There are more than 300,000 traffic lights in the US as of 2018. Let us assume that we placed a device on each of them to collect metrics and send it to a central metrics collection system. If each of the IOT devices sends 10 events per minute, we have 300000x10x60x24 = 432x10^7 events per day. How would you go about processing that and telling me how many of the signals were \u201cgreen\u201d at 10:45 am on a particular day? Consider the next example on Unified Payments Interface (UPI) transactions: We had about 1.15 billion UPI transactions in the month of October, 2019 in India. If we try to extrapolate this data to about a year and try to find out some common payments that were happening through a particular UPI ID, how do you suggest we go about that?","title":"Usage of Big Data techniques"},{"location":"git/branches/","text":"Working With Branches Coming back to our local repo which has two commits. So far, what we have is a single line of history. Commits are chained in a single line. But sometimes you may have a need to work on two different features in parallel in the same repo. Now one option here could be making a new folder/repo with the same code and use that for another feature development. But there's a better way. Use branches. Since git follows tree like structure for commits, we can use branches to work on different sets of features. From a commit, two or more branches can be created and branches can also be merged. Using branches, there can exist multiple lines of histories and we can checkout to any of them and work on it. Checking out, as we discussed earlier, would simply mean replacing contents of the directory (repo) with contents snapshot at the checked out version. Let's create a branch and see how it looks like: spatel1-mn1:school-of-sre spatel1$ git branch b1 spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph * 7f3b00e (HEAD - master, b1) adding file 2 * df2fb7a adding file 1 We create a branch called b1 . Git log tells us that b1 also points to the last commit (7f3b00e) but the HEAD is still pointing to master. If you remember, HEAD points to the commit/reference wherever you are checkout to. So if we checkout to b1 , HEAD should point to that. Let's confirm: spatel1-mn1:school-of-sre spatel1$ git checkout b1 Switched to branch 'b1' spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph * 7f3b00e (HEAD - b1, master) adding file 2 * df2fb7a adding file 1 b1 still points to the same commit but HEAD now points to b1 . Since we create a branch at commit 7f3b00e , there will be two lines of histories starting this commit. Depending on which branch you are checked out on, the line of history will progress. At this moment, we are checked out on branch b1 , so making a new commit will advance branch reference b1 to that commit and current b1 commit will become its parent. Let's do that. # Creating a file and making a commit spatel1-mn1:school-of-sre spatel1$ echo I am a file in b1 branch b1.txt spatel1-mn1:school-of-sre spatel1$ git add b1.txt spatel1-mn1:school-of-sre spatel1$ git commit -m adding b1 file [b1 872a38f] adding b1 file 1 file changed, 1 insertion(+) create mode 100644 b1.txt # The new line of history spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph * 872a38f (HEAD - b1) adding b1 file * 7f3b00e (master) adding file 2 * df2fb7a adding file 1 spatel1-mn1:school-of-sre spatel1$ Do note that master is still pointing to the old commit it was pointing to. We can now checkout to master branch and make commits there. This will result in another line of history starting from commit 7f3b00e. # checkout to master branch spatel1-mn1:school-of-sre spatel1$ git checkout master Switched to branch 'master' # Creating a new commit on master branch spatel1-mn1:school-of-sre spatel1$ echo new file in master branch master.txt spatel1-mn1:school-of-sre spatel1$ git add master.txt spatel1-mn1:school-of-sre spatel1$ git commit -m adding master.txt file [master 60dc441] adding master.txt file 1 file changed, 1 insertion(+) create mode 100644 master.txt # The history line spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph * 60dc441 (HEAD - master) adding master.txt file * 7f3b00e adding file 2 * df2fb7a adding file 1 Notice how branch b1 is not visible here since we are checkout on master. Let's try to visualize both to get the whole picture: spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph --all * 60dc441 (HEAD - master) adding master.txt file | * 872a38f (b1) adding b1 file |/ * 7f3b00e adding file 2 * df2fb7a adding file 1 Above tree structure should make things clear. Notice a clear branch/fork on commit 7f3b00e. This is how we create branches. Now they both are two separate lines of history on which feature development can be done independently. To reiterate, internally, git is just a tree of commits. Branch names (human readable) are pointers to those commits in the tree. We use various git commands to work with the tree structure and references. Git accordingly modifies contents of our repo. Merges Now say the feature you were working on branch b1 is complete. And you need to merge it on master branch, where all the final version of code goes. So first you will checkout to branch master and then you will pull the latest code from upstream (eg: GitHub). Then you need to merge your code from b1 into master. And there could be two ways this can be done. Here is the current history: spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph --all * 60dc441 (HEAD - master) adding master.txt file | * 872a38f (b1) adding b1 file |/ * 7f3b00e adding file 2 * df2fb7a adding file 1 Option 1: Directly merge the branch. Merging the branch b1 into master will result in a new merge commit which will merge changes from two different lines of history and create a new commit of the result. spatel1-mn1:school-of-sre spatel1$ git merge b1 Merge made by the 'recursive' strategy. b1.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 b1.txt spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph --all * 8fc28f9 (HEAD - master) Merge branch 'b1' |\\ | * 872a38f (b1) adding b1 file * | 60dc441 adding master.txt file |/ * 7f3b00e adding file 2 * df2fb7a adding file 1 You can see a new merge commit created (8fc28f9). You will be prompted for the commit message. If there are a lot of branches in the repo, this result will end-up with a lot of merge commits. Which looks ugly compared to a single line of history of development. So let's look at an alternative approach First let's reset our last merge and go to the previous state. spatel1-mn1:school-of-sre spatel1$ git reset --hard 60dc441 HEAD is now at 60dc441 adding master.txt file spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph --all * 60dc441 (HEAD - master) adding master.txt file | * 872a38f (b1) adding b1 file |/ * 7f3b00e adding file 2 * df2fb7a adding file 1 Option 2: Rebase. Now, instead of merging two branches which has a similar base (commit: 7f3b00e), let us rebase branch b1 on to current master. What this means is take branch b1 (from commit 7f3b00e to commit 872a38f) and rebase (put them on top of) master (60dc441). # Switch to b1 spatel1-mn1:school-of-sre spatel1$ git checkout b1 Switched to branch 'b1' # Rebase (b1 which is current branch) on master spatel1-mn1:school-of-sre spatel1$ git rebase master First, rewinding head to replay your work on top of it... Applying: adding b1 file # The result spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph --all * 5372c8f (HEAD - b1) adding b1 file * 60dc441 (master) adding master.txt file * 7f3b00e adding file 2 * df2fb7a adding file 1 You can see b1 which had 1 commit. That commit's parent was 7f3b00e . But since we rebase it on master ( 60dc441 ). That becomes the parent now. As a side effect, you also see it has become a single line of history. Now if we were to merge b1 into master , it would simply mean change master to point to 5372c8f which is b1 . Let's try it: # checkout to master since we want to merge code into master spatel1-mn1:school-of-sre spatel1$ git checkout master Switched to branch 'master' # the current history, where b1 is based on master spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph --all * 5372c8f (b1) adding b1 file * 60dc441 (HEAD - master) adding master.txt file * 7f3b00e adding file 2 * df2fb7a adding file 1 # Performing the merge, notice the fast-forward message spatel1-mn1:school-of-sre spatel1$ git merge b1 Updating 60dc441..5372c8f Fast-forward b1.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 b1.txt # The Result spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph --all * 5372c8f (HEAD - master, b1) adding b1 file * 60dc441 adding master.txt file * 7f3b00e adding file 2 * df2fb7a adding file 1 Now you see both b1 and master are pointing to the same commit. Your code has been merged to the master branch and it can be pushed. Also we have clean line of history! :D","title":"Working With Branches"},{"location":"git/branches/#working-with-branches","text":"Coming back to our local repo which has two commits. So far, what we have is a single line of history. Commits are chained in a single line. But sometimes you may have a need to work on two different features in parallel in the same repo. Now one option here could be making a new folder/repo with the same code and use that for another feature development. But there's a better way. Use branches. Since git follows tree like structure for commits, we can use branches to work on different sets of features. From a commit, two or more branches can be created and branches can also be merged. Using branches, there can exist multiple lines of histories and we can checkout to any of them and work on it. Checking out, as we discussed earlier, would simply mean replacing contents of the directory (repo) with contents snapshot at the checked out version. Let's create a branch and see how it looks like: spatel1-mn1:school-of-sre spatel1$ git branch b1 spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph * 7f3b00e (HEAD - master, b1) adding file 2 * df2fb7a adding file 1 We create a branch called b1 . Git log tells us that b1 also points to the last commit (7f3b00e) but the HEAD is still pointing to master. If you remember, HEAD points to the commit/reference wherever you are checkout to. So if we checkout to b1 , HEAD should point to that. Let's confirm: spatel1-mn1:school-of-sre spatel1$ git checkout b1 Switched to branch 'b1' spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph * 7f3b00e (HEAD - b1, master) adding file 2 * df2fb7a adding file 1 b1 still points to the same commit but HEAD now points to b1 . Since we create a branch at commit 7f3b00e , there will be two lines of histories starting this commit. Depending on which branch you are checked out on, the line of history will progress. At this moment, we are checked out on branch b1 , so making a new commit will advance branch reference b1 to that commit and current b1 commit will become its parent. Let's do that. # Creating a file and making a commit spatel1-mn1:school-of-sre spatel1$ echo I am a file in b1 branch b1.txt spatel1-mn1:school-of-sre spatel1$ git add b1.txt spatel1-mn1:school-of-sre spatel1$ git commit -m adding b1 file [b1 872a38f] adding b1 file 1 file changed, 1 insertion(+) create mode 100644 b1.txt # The new line of history spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph * 872a38f (HEAD - b1) adding b1 file * 7f3b00e (master) adding file 2 * df2fb7a adding file 1 spatel1-mn1:school-of-sre spatel1$ Do note that master is still pointing to the old commit it was pointing to. We can now checkout to master branch and make commits there. This will result in another line of history starting from commit 7f3b00e. # checkout to master branch spatel1-mn1:school-of-sre spatel1$ git checkout master Switched to branch 'master' # Creating a new commit on master branch spatel1-mn1:school-of-sre spatel1$ echo new file in master branch master.txt spatel1-mn1:school-of-sre spatel1$ git add master.txt spatel1-mn1:school-of-sre spatel1$ git commit -m adding master.txt file [master 60dc441] adding master.txt file 1 file changed, 1 insertion(+) create mode 100644 master.txt # The history line spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph * 60dc441 (HEAD - master) adding master.txt file * 7f3b00e adding file 2 * df2fb7a adding file 1 Notice how branch b1 is not visible here since we are checkout on master. Let's try to visualize both to get the whole picture: spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph --all * 60dc441 (HEAD - master) adding master.txt file | * 872a38f (b1) adding b1 file |/ * 7f3b00e adding file 2 * df2fb7a adding file 1 Above tree structure should make things clear. Notice a clear branch/fork on commit 7f3b00e. This is how we create branches. Now they both are two separate lines of history on which feature development can be done independently. To reiterate, internally, git is just a tree of commits. Branch names (human readable) are pointers to those commits in the tree. We use various git commands to work with the tree structure and references. Git accordingly modifies contents of our repo.","title":"Working With Branches"},{"location":"git/branches/#merges","text":"Now say the feature you were working on branch b1 is complete. And you need to merge it on master branch, where all the final version of code goes. So first you will checkout to branch master and then you will pull the latest code from upstream (eg: GitHub). Then you need to merge your code from b1 into master. And there could be two ways this can be done. Here is the current history: spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph --all * 60dc441 (HEAD - master) adding master.txt file | * 872a38f (b1) adding b1 file |/ * 7f3b00e adding file 2 * df2fb7a adding file 1 Option 1: Directly merge the branch. Merging the branch b1 into master will result in a new merge commit which will merge changes from two different lines of history and create a new commit of the result. spatel1-mn1:school-of-sre spatel1$ git merge b1 Merge made by the 'recursive' strategy. b1.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 b1.txt spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph --all * 8fc28f9 (HEAD - master) Merge branch 'b1' |\\ | * 872a38f (b1) adding b1 file * | 60dc441 adding master.txt file |/ * 7f3b00e adding file 2 * df2fb7a adding file 1 You can see a new merge commit created (8fc28f9). You will be prompted for the commit message. If there are a lot of branches in the repo, this result will end-up with a lot of merge commits. Which looks ugly compared to a single line of history of development. So let's look at an alternative approach First let's reset our last merge and go to the previous state. spatel1-mn1:school-of-sre spatel1$ git reset --hard 60dc441 HEAD is now at 60dc441 adding master.txt file spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph --all * 60dc441 (HEAD - master) adding master.txt file | * 872a38f (b1) adding b1 file |/ * 7f3b00e adding file 2 * df2fb7a adding file 1 Option 2: Rebase. Now, instead of merging two branches which has a similar base (commit: 7f3b00e), let us rebase branch b1 on to current master. What this means is take branch b1 (from commit 7f3b00e to commit 872a38f) and rebase (put them on top of) master (60dc441). # Switch to b1 spatel1-mn1:school-of-sre spatel1$ git checkout b1 Switched to branch 'b1' # Rebase (b1 which is current branch) on master spatel1-mn1:school-of-sre spatel1$ git rebase master First, rewinding head to replay your work on top of it... Applying: adding b1 file # The result spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph --all * 5372c8f (HEAD - b1) adding b1 file * 60dc441 (master) adding master.txt file * 7f3b00e adding file 2 * df2fb7a adding file 1 You can see b1 which had 1 commit. That commit's parent was 7f3b00e . But since we rebase it on master ( 60dc441 ). That becomes the parent now. As a side effect, you also see it has become a single line of history. Now if we were to merge b1 into master , it would simply mean change master to point to 5372c8f which is b1 . Let's try it: # checkout to master since we want to merge code into master spatel1-mn1:school-of-sre spatel1$ git checkout master Switched to branch 'master' # the current history, where b1 is based on master spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph --all * 5372c8f (b1) adding b1 file * 60dc441 (HEAD - master) adding master.txt file * 7f3b00e adding file 2 * df2fb7a adding file 1 # Performing the merge, notice the fast-forward message spatel1-mn1:school-of-sre spatel1$ git merge b1 Updating 60dc441..5372c8f Fast-forward b1.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 b1.txt # The Result spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph --all * 5372c8f (HEAD - master, b1) adding b1 file * 60dc441 adding master.txt file * 7f3b00e adding file 2 * df2fb7a adding file 1 Now you see both b1 and master are pointing to the same commit. Your code has been merged to the master branch and it can be pushed. Also we have clean line of history! :D","title":"Merges"},{"location":"git/git-basics/","text":"School Of SRE: Git Pre - Reads Have Git installed https://git-scm.com/downloads Have taken any git high level tutorial or following LinkedIn learning courses https://www.linkedin.com/learning/git-essential-training-the-basics/ https://www.linkedin.com/learning/git-branches-merges-and-remotes/ The Official Git Docs What to expect from this training As an engineer in the field of computer science, having knowledge of version control tools becomes almost a requirement. While there are a lot of version control tools that exist today, Git perhaps is the most used one and this course we will be working with Git. While this course does not start with Git 101 and expects basic knowledge of git as a prerequisite, it will reintroduce the git concepts known by you with details covering what is happening under the hood as you execute various git commands. So that next time you run a git command, you will be able to press enter more confidently! What is not covered under this training Advanced usage and specifics of internal implementation details of Git. Training Content Table of Contents Git Basics Working with Branches Git with Github Hooks Git Basics Though you might be aware already, let's revisit why we need a version control system. As the project grows and multiple developers start working on it, an efficient method for collaboration is warranted. Git helps the team collaborate easily and also maintains history of the changes happened with the codebase. Creating a Git Repo Any folder can be converted into a git repository. After executing the following command, we will see a .git folder within the folder, which makes our folder a git repository. All the magic that git does, .git folder is the enabler for the same. # creating an empty folder and changing current dir to it spatel1-mn1:~ spatel1$ cd /tmp spatel1-mn1:tmp spatel1$ mkdir school-of-sre spatel1-mn1:tmp spatel1$ cd school-of-sre/ # initialize a git repo spatel1-mn1:school-of-sre spatel1$ git init Initialized empty Git repository in /private/tmp/school-of-sre/.git/ As the output says, an empty git repo has been initialized in our folder. Let's take a look at what is there. spatel1-mn1:school-of-sre spatel1$ ls .git/ HEAD config description hooks info objects refs There are a bunch of folders and files in the .git folder. As I said, all these enables git to do its magic. We will look into some of these folders and files. But for now, what we have is an empty git repository. Tracking a File Now as you might already know, let us create a new file in our repo (we will refer to the folder as repo now.) And see git status spatel1-mn1:school-of-sre spatel1$ echo I am file 1 file1.txt spatel1-mn1:school-of-sre spatel1$ git status On branch master No commits yet Untracked files: (use git add file ... to include in what will be committed) file1.txt nothing added to commit but untracked files present (use git add to track) The current git status says No commits yet and there is one untracked file. Since we just created the file, git is not tracking that file. We explicitly need to ask git to track files and folders. (also checkout gitignore ) And how we do that is via git add command as suggested in the above output. Then we go ahead and create a commit. spatel1-mn1:school-of-sre spatel1$ git add file1.txt spatel1-mn1:school-of-sre spatel1$ git status On branch master No commits yet Changes to be committed: (use git rm --cached file ... to unstage) new file: file1.txt spatel1-mn1:school-of-sre spatel1$ git commit -m adding file 1 [master (root-commit) df2fb7a] adding file 1 1 file changed, 1 insertion(+) create mode 100644 file1.txt Notice how after adding the file, git status says Changes to be commited: . What it means is whatever is listed there, will be included in the next commit. Then we go ahead and create a commit, with an attached messaged via -m . More About a Commit Commit is a snapshot of the repo. Whenever a commit is made, a snapshot of the current state of repo (the folder) is taken and saved. Each commit has a unique ID. ( df2fb7a for the commit we made in the previous step). As we keep adding/changing more and more contents and keep making commits, all those snapshots are stored by git. Again, all this magic happens inside the .git folder. This is where all this snapshot or versions are stored. In an efficient manner. Adding More Changes Let us create one more file and commit the change. It would look the same as the previous commit we made. spatel1-mn1:school-of-sre spatel1$ echo I am file 2 file2.txt spatel1-mn1:school-of-sre spatel1$ git add file2.txt spatel1-mn1:school-of-sre spatel1$ git commit -m adding file 2 [master 7f3b00e] adding file 2 1 file changed, 1 insertion(+) create mode 100644 file2.txt A new commit with ID 7f3b00e has been created. You can issue git status at any time to see the state of the repository. **IMPORTANT: Note that commit IDs are long string (SHA) but we can refer to a commit by its initial few (8 or more) characters too. We will interchangeably using shorter and longer commit IDs.** Now that we have two commits, let's visualize them: spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph * 7f3b00e (HEAD - master) adding file 2 * df2fb7a adding file 1 git log , as the name suggests, prints the log of all the git commits. Here you see two additional arguments, --oneline prints the shorter version of the log, ie: the commit message only and not the person who made the commit and when. --graph prints it in graph format. Now at this moment the commits might look like just one in each line but all commits are stored as a tree like data structure internally by git. That means there can be two or more children commits of a given commit. And not just a single line of commits. We will look more into this part when we get to the Branches section. For now this is our commit history: df2fb7a === 7f3b00e Are commits really linked? As I just said, the two commits we just made are linked via tree like data structure and we saw how they are linked. But let's actually verify it. Everything in git is an object. Newly created files are stored as an object. Changes to file are stored as an objects and even commits are objects. To view contents of an object we can use the following command with the object's ID. We will take a look at content of the contents of the second commit spatel1-mn1:school-of-sre spatel1$ git cat-file -p 7f3b00e tree ebf3af44d253e5328340026e45a9fa9ae3ea1982 parent df2fb7a61f5d40c1191e0fdeb0fc5d6e7969685a author Sanket Patel spatel1@linkedin.com 1603273316 -0700 committer Sanket Patel spatel1@linkedin.com 1603273316 -0700 adding file 2 Take a note of parent attribute in the above output. It points to the commit id of the first commit we made. So this proves that they are linked! Additionally you can see the second commit's message in this object. As I said all this magic is enabled by .git folder and the object to which we are looking at also is in that folder. spatel1-mn1:school-of-sre spatel1$ ls .git/objects/7f/3b00eaa957815884198e2fdfec29361108d6a9 .git/objects/7f/3b00eaa957815884198e2fdfec29361108d6a9 It is stored in .git/objects/ folder. All the files and changes to them as well are stored in this folder. The Version Control part of Git We already can see two commits (versions) in our git log. One thing a version control tool gives you is ability to browse back and forth in history. For example: some of your users are running an old version of code and they are reporting an issue. In order to debug the issue, you need access to the old code. The one in your current repo is the latest code. In this example, you are working on the second commit (7f3b00e) and someone reported an issue with the code snapshot at commit (df2fb7a). This is how you would get access to the code at any older commit # Current contents, two files present patel1-mn1:school-of-sre spatel1$ ls file1.txt file2.txt # checking out to (an older) commit spatel1-mn1:school-of-sre spatel1$ git checkout df2fb7a Note: checking out 'df2fb7a'. You are in 'detached HEAD' state. You can look around, make experimental changes and commit them, and you can discard any commits you make in this state without impacting any branches by performing another checkout. If you want to create a new branch to retain commits you create, you may do so (now or later) by using -b with the checkout command again. Example: git checkout -b new-branch-name HEAD is now at df2fb7a adding file 1 # checking contents, can verify it has old contents spatel1-mn1:school-of-sre spatel1$ ls file1.txt So this is how we would get access to old versions/snapshots. All we need is a reference to that snapshot. Upon executing git checkout ... , what git does for you is use the .git folder, see what was the state of things (files and folders) at that version/reference and replace the contents of current directory with those contents. The then-existing content will no longer be present in the local dir (repo) but we can and will still get access to them because they are tracked via git commit and .git folder has them stored/tracked. Reference I mention in the previous section that we need a reference to the version. By default, git repo is made of tree of commits. And each commit has a unique IDs. But the unique ID is not the only thing we can reference commits via. There are multiple ways to reference commits. For example: HEAD is a reference to current commit. Whatever commit your repo is checked out at, HEAD will point to that. HEAD~1 is reference to previous commit. So while checking out previous version in section above, we could have done git checkout HEAD~1 . Similarly, master is also a reference (to a branch). Since git uses tree like structure to store commits, there of course will be branches. And the default branch is called master . Master (or any branch reference) will point to the latest commit in the branch. Even though we have checked out to the previous commit in out repo, master still points to the latest commit. And we can get back to the latest version by checkout at master reference spatel1-mn1:school-of-sre spatel1$ git checkout master Previous HEAD position was df2fb7a adding file 1 Switched to branch 'master' # now we will see latest code, with two files spatel1-mn1:school-of-sre spatel1$ ls file1.txt file2.txt Note, instead of master in above command, we could have used commit's ID as well. References and The Magic Let's look at the state of things. Two commits, master and HEAD references are pointing to the latest commit spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph * 7f3b00e (HEAD - master) adding file 2 * df2fb7a adding file 1 The magic? Let's examine these files: spatel1-mn1:school-of-sre spatel1$ cat .git/refs/heads/master 7f3b00eaa957815884198e2fdfec29361108d6a9 Viola! Where master is pointing to is stored in a file. Whenever git needs to know where master reference is pointing to, or if git needs to update where master points, it just needs to update the file above. So when you create a new commit, a new commit is created on top of the current commit and the master file is updated with the new commit's ID. Similary, for HEAD reference: spatel1-mn1:school-of-sre spatel1$ cat .git/HEAD ref: refs/heads/master We can see HEAD is pointing to a reference called refs/heads/master . So HEAD will point where ever the master points. Little Adventure We discussed how git will update the files as we execute commands. But let's try to do it ourselves, by hand, and see what happens. spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph * 7f3b00e (HEAD - master) adding file 2 * df2fb7a adding file 1 Now let's change master to point to the previous/first commit. spatel1-mn1:school-of-sre spatel1$ echo df2fb7a61f5d40c1191e0fdeb0fc5d6e7969685a .git/refs/heads/master spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph * df2fb7a (HEAD - master) adding file 1 # RESETTING TO ORIGINAL spatel1-mn1:school-of-sre spatel1$ echo 7f3b00eaa957815884198e2fdfec29361108d6a9 .git/refs/heads/master spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph * 7f3b00e (HEAD - master) adding file 2 * df2fb7a adding file 1 We just edited the master reference file and now we can see only the first commit in git log. Undoing the change to the file brings the state back to original. Not so much of magic, isn't it?","title":"Git Basics"},{"location":"git/git-basics/#school-of-sre-git","text":"","title":"School Of SRE: Git"},{"location":"git/git-basics/#pre-reads","text":"Have Git installed https://git-scm.com/downloads Have taken any git high level tutorial or following LinkedIn learning courses https://www.linkedin.com/learning/git-essential-training-the-basics/ https://www.linkedin.com/learning/git-branches-merges-and-remotes/ The Official Git Docs","title":"Pre - Reads"},{"location":"git/git-basics/#what-to-expect-from-this-training","text":"As an engineer in the field of computer science, having knowledge of version control tools becomes almost a requirement. While there are a lot of version control tools that exist today, Git perhaps is the most used one and this course we will be working with Git. While this course does not start with Git 101 and expects basic knowledge of git as a prerequisite, it will reintroduce the git concepts known by you with details covering what is happening under the hood as you execute various git commands. So that next time you run a git command, you will be able to press enter more confidently!","title":"What to expect from this training"},{"location":"git/git-basics/#what-is-not-covered-under-this-training","text":"Advanced usage and specifics of internal implementation details of Git.","title":"What is not covered under this training"},{"location":"git/git-basics/#training-content","text":"","title":"Training Content"},{"location":"git/git-basics/#table-of-contents","text":"Git Basics Working with Branches Git with Github Hooks","title":"Table of Contents"},{"location":"git/git-basics/#git-basics","text":"Though you might be aware already, let's revisit why we need a version control system. As the project grows and multiple developers start working on it, an efficient method for collaboration is warranted. Git helps the team collaborate easily and also maintains history of the changes happened with the codebase.","title":"Git Basics"},{"location":"git/git-basics/#creating-a-git-repo","text":"Any folder can be converted into a git repository. After executing the following command, we will see a .git folder within the folder, which makes our folder a git repository. All the magic that git does, .git folder is the enabler for the same. # creating an empty folder and changing current dir to it spatel1-mn1:~ spatel1$ cd /tmp spatel1-mn1:tmp spatel1$ mkdir school-of-sre spatel1-mn1:tmp spatel1$ cd school-of-sre/ # initialize a git repo spatel1-mn1:school-of-sre spatel1$ git init Initialized empty Git repository in /private/tmp/school-of-sre/.git/ As the output says, an empty git repo has been initialized in our folder. Let's take a look at what is there. spatel1-mn1:school-of-sre spatel1$ ls .git/ HEAD config description hooks info objects refs There are a bunch of folders and files in the .git folder. As I said, all these enables git to do its magic. We will look into some of these folders and files. But for now, what we have is an empty git repository.","title":"Creating a Git Repo"},{"location":"git/git-basics/#tracking-a-file","text":"Now as you might already know, let us create a new file in our repo (we will refer to the folder as repo now.) And see git status spatel1-mn1:school-of-sre spatel1$ echo I am file 1 file1.txt spatel1-mn1:school-of-sre spatel1$ git status On branch master No commits yet Untracked files: (use git add file ... to include in what will be committed) file1.txt nothing added to commit but untracked files present (use git add to track) The current git status says No commits yet and there is one untracked file. Since we just created the file, git is not tracking that file. We explicitly need to ask git to track files and folders. (also checkout gitignore ) And how we do that is via git add command as suggested in the above output. Then we go ahead and create a commit. spatel1-mn1:school-of-sre spatel1$ git add file1.txt spatel1-mn1:school-of-sre spatel1$ git status On branch master No commits yet Changes to be committed: (use git rm --cached file ... to unstage) new file: file1.txt spatel1-mn1:school-of-sre spatel1$ git commit -m adding file 1 [master (root-commit) df2fb7a] adding file 1 1 file changed, 1 insertion(+) create mode 100644 file1.txt Notice how after adding the file, git status says Changes to be commited: . What it means is whatever is listed there, will be included in the next commit. Then we go ahead and create a commit, with an attached messaged via -m .","title":"Tracking a File"},{"location":"git/git-basics/#more-about-a-commit","text":"Commit is a snapshot of the repo. Whenever a commit is made, a snapshot of the current state of repo (the folder) is taken and saved. Each commit has a unique ID. ( df2fb7a for the commit we made in the previous step). As we keep adding/changing more and more contents and keep making commits, all those snapshots are stored by git. Again, all this magic happens inside the .git folder. This is where all this snapshot or versions are stored. In an efficient manner.","title":"More About a Commit"},{"location":"git/git-basics/#adding-more-changes","text":"Let us create one more file and commit the change. It would look the same as the previous commit we made. spatel1-mn1:school-of-sre spatel1$ echo I am file 2 file2.txt spatel1-mn1:school-of-sre spatel1$ git add file2.txt spatel1-mn1:school-of-sre spatel1$ git commit -m adding file 2 [master 7f3b00e] adding file 2 1 file changed, 1 insertion(+) create mode 100644 file2.txt A new commit with ID 7f3b00e has been created. You can issue git status at any time to see the state of the repository. **IMPORTANT: Note that commit IDs are long string (SHA) but we can refer to a commit by its initial few (8 or more) characters too. We will interchangeably using shorter and longer commit IDs.** Now that we have two commits, let's visualize them: spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph * 7f3b00e (HEAD - master) adding file 2 * df2fb7a adding file 1 git log , as the name suggests, prints the log of all the git commits. Here you see two additional arguments, --oneline prints the shorter version of the log, ie: the commit message only and not the person who made the commit and when. --graph prints it in graph format. Now at this moment the commits might look like just one in each line but all commits are stored as a tree like data structure internally by git. That means there can be two or more children commits of a given commit. And not just a single line of commits. We will look more into this part when we get to the Branches section. For now this is our commit history: df2fb7a === 7f3b00e","title":"Adding More Changes"},{"location":"git/git-basics/#are-commits-really-linked","text":"As I just said, the two commits we just made are linked via tree like data structure and we saw how they are linked. But let's actually verify it. Everything in git is an object. Newly created files are stored as an object. Changes to file are stored as an objects and even commits are objects. To view contents of an object we can use the following command with the object's ID. We will take a look at content of the contents of the second commit spatel1-mn1:school-of-sre spatel1$ git cat-file -p 7f3b00e tree ebf3af44d253e5328340026e45a9fa9ae3ea1982 parent df2fb7a61f5d40c1191e0fdeb0fc5d6e7969685a author Sanket Patel spatel1@linkedin.com 1603273316 -0700 committer Sanket Patel spatel1@linkedin.com 1603273316 -0700 adding file 2 Take a note of parent attribute in the above output. It points to the commit id of the first commit we made. So this proves that they are linked! Additionally you can see the second commit's message in this object. As I said all this magic is enabled by .git folder and the object to which we are looking at also is in that folder. spatel1-mn1:school-of-sre spatel1$ ls .git/objects/7f/3b00eaa957815884198e2fdfec29361108d6a9 .git/objects/7f/3b00eaa957815884198e2fdfec29361108d6a9 It is stored in .git/objects/ folder. All the files and changes to them as well are stored in this folder.","title":"Are commits really linked?"},{"location":"git/git-basics/#the-version-control-part-of-git","text":"We already can see two commits (versions) in our git log. One thing a version control tool gives you is ability to browse back and forth in history. For example: some of your users are running an old version of code and they are reporting an issue. In order to debug the issue, you need access to the old code. The one in your current repo is the latest code. In this example, you are working on the second commit (7f3b00e) and someone reported an issue with the code snapshot at commit (df2fb7a). This is how you would get access to the code at any older commit # Current contents, two files present patel1-mn1:school-of-sre spatel1$ ls file1.txt file2.txt # checking out to (an older) commit spatel1-mn1:school-of-sre spatel1$ git checkout df2fb7a Note: checking out 'df2fb7a'. You are in 'detached HEAD' state. You can look around, make experimental changes and commit them, and you can discard any commits you make in this state without impacting any branches by performing another checkout. If you want to create a new branch to retain commits you create, you may do so (now or later) by using -b with the checkout command again. Example: git checkout -b new-branch-name HEAD is now at df2fb7a adding file 1 # checking contents, can verify it has old contents spatel1-mn1:school-of-sre spatel1$ ls file1.txt So this is how we would get access to old versions/snapshots. All we need is a reference to that snapshot. Upon executing git checkout ... , what git does for you is use the .git folder, see what was the state of things (files and folders) at that version/reference and replace the contents of current directory with those contents. The then-existing content will no longer be present in the local dir (repo) but we can and will still get access to them because they are tracked via git commit and .git folder has them stored/tracked.","title":"The Version Control part of Git"},{"location":"git/git-basics/#reference","text":"I mention in the previous section that we need a reference to the version. By default, git repo is made of tree of commits. And each commit has a unique IDs. But the unique ID is not the only thing we can reference commits via. There are multiple ways to reference commits. For example: HEAD is a reference to current commit. Whatever commit your repo is checked out at, HEAD will point to that. HEAD~1 is reference to previous commit. So while checking out previous version in section above, we could have done git checkout HEAD~1 . Similarly, master is also a reference (to a branch). Since git uses tree like structure to store commits, there of course will be branches. And the default branch is called master . Master (or any branch reference) will point to the latest commit in the branch. Even though we have checked out to the previous commit in out repo, master still points to the latest commit. And we can get back to the latest version by checkout at master reference spatel1-mn1:school-of-sre spatel1$ git checkout master Previous HEAD position was df2fb7a adding file 1 Switched to branch 'master' # now we will see latest code, with two files spatel1-mn1:school-of-sre spatel1$ ls file1.txt file2.txt Note, instead of master in above command, we could have used commit's ID as well.","title":"Reference"},{"location":"git/git-basics/#references-and-the-magic","text":"Let's look at the state of things. Two commits, master and HEAD references are pointing to the latest commit spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph * 7f3b00e (HEAD - master) adding file 2 * df2fb7a adding file 1 The magic? Let's examine these files: spatel1-mn1:school-of-sre spatel1$ cat .git/refs/heads/master 7f3b00eaa957815884198e2fdfec29361108d6a9 Viola! Where master is pointing to is stored in a file. Whenever git needs to know where master reference is pointing to, or if git needs to update where master points, it just needs to update the file above. So when you create a new commit, a new commit is created on top of the current commit and the master file is updated with the new commit's ID. Similary, for HEAD reference: spatel1-mn1:school-of-sre spatel1$ cat .git/HEAD ref: refs/heads/master We can see HEAD is pointing to a reference called refs/heads/master . So HEAD will point where ever the master points.","title":"References and The Magic"},{"location":"git/git-basics/#little-adventure","text":"We discussed how git will update the files as we execute commands. But let's try to do it ourselves, by hand, and see what happens. spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph * 7f3b00e (HEAD - master) adding file 2 * df2fb7a adding file 1 Now let's change master to point to the previous/first commit. spatel1-mn1:school-of-sre spatel1$ echo df2fb7a61f5d40c1191e0fdeb0fc5d6e7969685a .git/refs/heads/master spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph * df2fb7a (HEAD - master) adding file 1 # RESETTING TO ORIGINAL spatel1-mn1:school-of-sre spatel1$ echo 7f3b00eaa957815884198e2fdfec29361108d6a9 .git/refs/heads/master spatel1-mn1:school-of-sre spatel1$ git log --oneline --graph * 7f3b00e (HEAD - master) adding file 2 * df2fb7a adding file 1 We just edited the master reference file and now we can see only the first commit in git log. Undoing the change to the file brings the state back to original. Not so much of magic, isn't it?","title":"Little Adventure"},{"location":"git/github-hooks/","text":"Git with Github Till now all the operations we did were in our local repo while git also helps us in a collaborative environment. GitHub is one place on the internet where you can centrally host your git repos and collaborate with other developers. Most of the workflow will remain the same as we discussed, with addition of couple of things: Pull: to pull latest changes from github (the central) repo Push: to push your changes to github repo so that it's available to all people GitHub has written nice guides and tutorials about this and you can refer them here: GitHub Hello World Git Handbook Hooks Git has another nice feature called hooks. Hooks are basically scripts which will be called when a certain event happens. Here is where hooks are located: spatel1-mn1:school-of-sre spatel1$ ls .git/hooks/ applypatch-msg.sample fsmonitor-watchman.sample pre-applypatch.sample pre-push.sample pre-receive.sample update.sample commit-msg.sample post-update.sample pre-commit.sample pre-rebase.sample prepare-commit-msg.sample Names are self explanatory. These hooks are useful when you want to do certain things when a certain event happens. Ie: if you want to run tests before pushing code, you would want to setup pre-push hooks. Let's try to create a pre commit hook. spatel1-mn1:school-of-sre spatel1$ echo echo this is from pre commit hook .git/hooks/pre-commit spatel1-mn1:school-of-sre spatel1$ chmod +x .git/hooks/pre-commit We basically create a file called pre-commit in hooks folder and make it executable. Now if we make a commit, we should see the message getting printed. spatel1-mn1:school-of-sre spatel1$ echo sample file sample.txt spatel1-mn1:school-of-sre spatel1$ git add sample.txt spatel1-mn1:school-of-sre spatel1$ git commit -m adding sample file this is from pre commit hook # ===== THE MESSAGE FROM HOOK EXECUTION [master 9894e05] adding sample file 1 file changed, 1 insertion(+) create mode 100644 sample.txt What next from here? There are a lot of git commands and features which we have not explored here. But with the base built-up, be sure to explore concepts like Cherrypick Squash Amend Stash Reset","title":"Github and Hooks"},{"location":"git/github-hooks/#git-with-github","text":"Till now all the operations we did were in our local repo while git also helps us in a collaborative environment. GitHub is one place on the internet where you can centrally host your git repos and collaborate with other developers. Most of the workflow will remain the same as we discussed, with addition of couple of things: Pull: to pull latest changes from github (the central) repo Push: to push your changes to github repo so that it's available to all people GitHub has written nice guides and tutorials about this and you can refer them here: GitHub Hello World Git Handbook","title":"Git with Github"},{"location":"git/github-hooks/#hooks","text":"Git has another nice feature called hooks. Hooks are basically scripts which will be called when a certain event happens. Here is where hooks are located: spatel1-mn1:school-of-sre spatel1$ ls .git/hooks/ applypatch-msg.sample fsmonitor-watchman.sample pre-applypatch.sample pre-push.sample pre-receive.sample update.sample commit-msg.sample post-update.sample pre-commit.sample pre-rebase.sample prepare-commit-msg.sample Names are self explanatory. These hooks are useful when you want to do certain things when a certain event happens. Ie: if you want to run tests before pushing code, you would want to setup pre-push hooks. Let's try to create a pre commit hook. spatel1-mn1:school-of-sre spatel1$ echo echo this is from pre commit hook .git/hooks/pre-commit spatel1-mn1:school-of-sre spatel1$ chmod +x .git/hooks/pre-commit We basically create a file called pre-commit in hooks folder and make it executable. Now if we make a commit, we should see the message getting printed. spatel1-mn1:school-of-sre spatel1$ echo sample file sample.txt spatel1-mn1:school-of-sre spatel1$ git add sample.txt spatel1-mn1:school-of-sre spatel1$ git commit -m adding sample file this is from pre commit hook # ===== THE MESSAGE FROM HOOK EXECUTION [master 9894e05] adding sample file 1 file changed, 1 insertion(+) create mode 100644 sample.txt","title":"Hooks"},{"location":"git/github-hooks/#what-next-from-here","text":"There are a lot of git commands and features which we have not explored here. But with the base built-up, be sure to explore concepts like Cherrypick Squash Amend Stash Reset","title":"What next from here?"},{"location":"python_web/intro/","text":"School of SRE: Python and The Web Pre - Reads Basic understanding of python language. Basic familiarity with flask framework. What to expect from this training This course is divided into two high level parts. In the first part, assuming familiarity with python language\u2019s basic operations and syntax usage, we will dive a little deeper into understanding python as a language. We will compare python with other programming languages that you might already know like Java and C. We will also explore concepts of Python objects and with help of that, explore python features like decorators. In the second part which will revolve around the web, and also assume familiarity with the Flask framework, we will start from the socket module and work with HTTP requests. This will demystify how frameworks like flask work internally. And to introduce SRE flavour to the course, we will design, develop and deploy (in theory) a URL shortening application. We will emphasize parts of the whole process that are more important as an SRE of the said app/service. What is not covered under this training Extensive knowledge of python internals and advanced python. Training Content Lab Environment Setup Have latest version of python installed TOC The Python Language Some Python Concepts Python Gotchas Python and Web Sockets Flask The URL Shortening App Design Scaling The App Monitoring The App The Python Language Assuming you know a little bit of C/C++ and Java, let's try to discuss the following questions in context of those two languages and python. You might have heard that C/C++ is a compiled language while python is an interpreted language. Generally, with compiled language we first compile the program and then run the executable while in case of python we run the source code directly like python hello_world.py . While Java, being an interpreted language, still has a separate compilation step and then its run. So what's really the difference? Compiled vs. Interpreted This might sound a little weird to you: python, in a way is a compiled language! Python has a compiler built-in! It is obvious in the case of java since we compile it using a separate command ie: javac helloWorld.java and it will produce a .class file which we know as a bytecode . Well, python is very similar to that. One difference here is that there is no separate compile command/binary needed to run a python program. What is the difference then, between java and python? Well, Java's compiler is more strict and sophisticated. As you might know Java is a statically typed language. So the compiler is written in a way that it can verify types related errors during compile time. While python being a dynamic language, types are not known until a program is run. So in a way, python compiler is dumb (or, less strict). But there indeed is a compile step involved when a python program is run. You might have seen python bytecode files with .pyc extension. Here is how you can see bytecode for a given python program. # Create a Hello World spatel1-mn1:tmp spatel1$ echo print('hello world') hello_world.py # Making sure it runs spatel1-mn1:tmp spatel1$ python3 hello_world.py hello world # The bytecode of the given program spatel1-mn1:tmp spatel1$ python -m dis hello_world.py 1 0 LOAD_NAME 0 (print) 2 LOAD_CONST 0 ('hello world') 4 CALL_FUNCTION 1 6 POP_TOP 8 LOAD_CONST 1 (None) 10 RETURN_VALUE Read more about dis module here Now coming to C/C++, there of course is a compiler. But the output is different than what java/python compiler would produce. Compiling a C program would produce what we also know as machine code . As opposed to bytecode. Running The Programs We know compilation is involved in all 3 languages we are discussing. Just that the compilers are different in nature and they output different types of content. In case of C/C++, the output is machine code which can be directly read by your operating system. When you execute that program, your OS will know how exactly to run it. But this is not the case with bytecode. Those bytecodes are language specific. Python has its own set of bytecode defined (more in dis module) and so does java. So naturally, your operating system will not know how to run it. To run this bytecode, we have something called Virtual Machines. Ie: The JVM or the Python VM (CPython, Jython). These so called Virtual Machines are the programs which can read the bytecode and run it on a given operating system. Python has multiple VMs available. Cpython is a python VM implemented in C language, similarly Jython is a Java implementation of python VM. At the end of the day, what they should be capable of is to understand python language syntax, be able to compile it to bytecode and be able to run that bytecode. You can implement a python VM in any language! (And people do so, just because it can be done) The Operating System +------------------------------------+ | | | | | | hello_world.py Python bytecode | Python VM Process | | | +----------------+ +----------------+ | +----------------+ | |print(... | COMPILE |LOAD_CONST... | | |Reads bytecode | | | +--------------- + +------------------- +line by line | | | | | | | |and executes. | | | | | | | | | | +----------------+ +----------------+ | +----------------+ | | | | | | | hello_world.c OS Specific machinecode | A New Process | | | +----------------+ +----------------+ | +----------------+ | |void main() { | COMPILE | binary contents| | | binary contents| | | +--------------- + +------------------- + | | | | | | | | | | | | | | | | | | +----------------+ +----------------+ | +----------------+ | | (binary contents | | runs as is) | | | | | +------------------------------------+ Two things to note for above diagram: Generally, when we run a python program, a python VM process is started which reads the python source code, compiles it to byte code and run it in a single step. Compiling is not a separate step. Shown only for illustration purpose. Binaries generated for C like languages are not exactly run as is. Since there are multiple types of binaries (eg: ELF), there are more complicated steps involved in order to run a binary but we will not go into that since all that is done at OS level.","title":"Intro"},{"location":"python_web/intro/#school-of-sre-python-and-the-web","text":"","title":"School of SRE: Python and The Web"},{"location":"python_web/intro/#pre-reads","text":"Basic understanding of python language. Basic familiarity with flask framework.","title":"Pre - Reads"},{"location":"python_web/intro/#what-to-expect-from-this-training","text":"This course is divided into two high level parts. In the first part, assuming familiarity with python language\u2019s basic operations and syntax usage, we will dive a little deeper into understanding python as a language. We will compare python with other programming languages that you might already know like Java and C. We will also explore concepts of Python objects and with help of that, explore python features like decorators. In the second part which will revolve around the web, and also assume familiarity with the Flask framework, we will start from the socket module and work with HTTP requests. This will demystify how frameworks like flask work internally. And to introduce SRE flavour to the course, we will design, develop and deploy (in theory) a URL shortening application. We will emphasize parts of the whole process that are more important as an SRE of the said app/service.","title":"What to expect from this training"},{"location":"python_web/intro/#what-is-not-covered-under-this-training","text":"Extensive knowledge of python internals and advanced python.","title":"What is not covered under this training"},{"location":"python_web/intro/#training-content","text":"","title":"Training Content"},{"location":"python_web/intro/#lab-environment-setup","text":"Have latest version of python installed","title":"Lab Environment Setup"},{"location":"python_web/intro/#toc","text":"The Python Language Some Python Concepts Python Gotchas Python and Web Sockets Flask The URL Shortening App Design Scaling The App Monitoring The App","title":"TOC"},{"location":"python_web/intro/#the-python-language","text":"Assuming you know a little bit of C/C++ and Java, let's try to discuss the following questions in context of those two languages and python. You might have heard that C/C++ is a compiled language while python is an interpreted language. Generally, with compiled language we first compile the program and then run the executable while in case of python we run the source code directly like python hello_world.py . While Java, being an interpreted language, still has a separate compilation step and then its run. So what's really the difference?","title":"The Python Language"},{"location":"python_web/intro/#compiled-vs-interpreted","text":"This might sound a little weird to you: python, in a way is a compiled language! Python has a compiler built-in! It is obvious in the case of java since we compile it using a separate command ie: javac helloWorld.java and it will produce a .class file which we know as a bytecode . Well, python is very similar to that. One difference here is that there is no separate compile command/binary needed to run a python program. What is the difference then, between java and python? Well, Java's compiler is more strict and sophisticated. As you might know Java is a statically typed language. So the compiler is written in a way that it can verify types related errors during compile time. While python being a dynamic language, types are not known until a program is run. So in a way, python compiler is dumb (or, less strict). But there indeed is a compile step involved when a python program is run. You might have seen python bytecode files with .pyc extension. Here is how you can see bytecode for a given python program. # Create a Hello World spatel1-mn1:tmp spatel1$ echo print('hello world') hello_world.py # Making sure it runs spatel1-mn1:tmp spatel1$ python3 hello_world.py hello world # The bytecode of the given program spatel1-mn1:tmp spatel1$ python -m dis hello_world.py 1 0 LOAD_NAME 0 (print) 2 LOAD_CONST 0 ('hello world') 4 CALL_FUNCTION 1 6 POP_TOP 8 LOAD_CONST 1 (None) 10 RETURN_VALUE Read more about dis module here Now coming to C/C++, there of course is a compiler. But the output is different than what java/python compiler would produce. Compiling a C program would produce what we also know as machine code . As opposed to bytecode.","title":"Compiled vs. Interpreted"},{"location":"python_web/intro/#running-the-programs","text":"We know compilation is involved in all 3 languages we are discussing. Just that the compilers are different in nature and they output different types of content. In case of C/C++, the output is machine code which can be directly read by your operating system. When you execute that program, your OS will know how exactly to run it. But this is not the case with bytecode. Those bytecodes are language specific. Python has its own set of bytecode defined (more in dis module) and so does java. So naturally, your operating system will not know how to run it. To run this bytecode, we have something called Virtual Machines. Ie: The JVM or the Python VM (CPython, Jython). These so called Virtual Machines are the programs which can read the bytecode and run it on a given operating system. Python has multiple VMs available. Cpython is a python VM implemented in C language, similarly Jython is a Java implementation of python VM. At the end of the day, what they should be capable of is to understand python language syntax, be able to compile it to bytecode and be able to run that bytecode. You can implement a python VM in any language! (And people do so, just because it can be done) The Operating System +------------------------------------+ | | | | | | hello_world.py Python bytecode | Python VM Process | | | +----------------+ +----------------+ | +----------------+ | |print(... | COMPILE |LOAD_CONST... | | |Reads bytecode | | | +--------------- + +------------------- +line by line | | | | | | | |and executes. | | | | | | | | | | +----------------+ +----------------+ | +----------------+ | | | | | | | hello_world.c OS Specific machinecode | A New Process | | | +----------------+ +----------------+ | +----------------+ | |void main() { | COMPILE | binary contents| | | binary contents| | | +--------------- + +------------------- + | | | | | | | | | | | | | | | | | | +----------------+ +----------------+ | +----------------+ | | (binary contents | | runs as is) | | | | | +------------------------------------+ Two things to note for above diagram: Generally, when we run a python program, a python VM process is started which reads the python source code, compiles it to byte code and run it in a single step. Compiling is not a separate step. Shown only for illustration purpose. Binaries generated for C like languages are not exactly run as is. Since there are multiple types of binaries (eg: ELF), there are more complicated steps involved in order to run a binary but we will not go into that since all that is done at OS level.","title":"Running The Programs"},{"location":"python_web/python-concepts/","text":"Some Python Concepts Though you are expected to know python and its syntax at basic level, let us discuss some fundamental concepts that will help you understand the python language better. Everything in Python is an object. That includes the functions, lists, dicts, classes, modules, a running function (instance of function definition), everything. In the CPython, it would mean there is an underlying struct variable for each object. In python's current execution context, all the variables are stored in a dict. It'd be a string to object mapping. If you have a function and a float variable defined in the current context, here is how it is handled internally. float_number=42.0 def foo_func(): ... pass ... # NOTICE HOW VARIABLE NAMES ARE STRINGS, stored in a dict locals() {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': class '_frozen_importlib.BuiltinImporter' , '__spec__': None, '__annotations__': {}, '__builtins__': module 'builtins' (built-in) , 'float_number': 42.0, 'foo_func': function foo_func at 0x1055847a0 } Python Functions Since functions too are objects, we can see what all attributes a function contains as following def hello(name): ... print(f Hello, {name}! ) ... dir(hello) ['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__'] While there are a lot of them, let's look at some interesting ones globals This attribute, as the name suggests, has references of global variables. If you ever need to know what all global variables are in the scope of this function, this will tell you. See how the function start seeing the new variable in globals hello.__globals__ {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': class '_frozen_importlib.BuiltinImporter' , '__spec__': None, '__annotations__': {}, '__builtins__': module 'builtins' (built-in) , 'hello': function hello at 0x7fe4e82554c0 } # adding new global variable GLOBAL= g_val hello.__globals__ {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': class '_frozen_importlib.BuiltinImporter' , '__spec__': None, '__annotations__': {}, '__builtins__': module 'builtins' (built-in) , 'hello': function hello at 0x7fe4e82554c0 , 'GLOBAL': 'g_val'} code This is an interesting one! As everything in python is an object, this includes the bytecode too. The compiled python bytecode is a python code object. Which is accessible via __code__ attribute here. A function has an associated code object which carries some interesting information. # the file in which function is defined # stdin here since this is run in an interpreter hello.__code__.co_filename ' stdin ' # number of arguments the function takes hello.__code__.co_argcount 1 # local variable names hello.__code__.co_varnames ('name',) # the function code's compiled bytecode hello.__code__.co_code b't\\x00d\\x01|\\x00\\x9b\\x00d\\x02\\x9d\\x03\\x83\\x01\\x01\\x00d\\x00S\\x00' There are more code attributes which you can enlist by dir(hello.__code__) Decorators Related to functions, python has another feature called decorators. Let's see how that works, keeping everything is an object in mind. Here is a sample decorator: def deco(func): ... def inner(): ... print( before ) ... func() ... print( after ) ... return inner ... @deco ... def hello_world(): ... print( hello world ) ... hello_world() before hello world after Here @deco syntax is used to decorate the hello_world function. It is essentially same as doing def hello_world(): ... print( hello world ) ... hello_world = deco(hello_world) What goes inside the deco function might seem complex. Let's try to uncover it. Function hello_world is created It is passed to deco function deco create a new function This new function is calls hello_world function And does a couple other things deco returns the newly created function hello_world is replaced with above function Let's visualize it for better understanding BEFORE function_object (ID: 100) hello_world +--------------------+ + |print( hello_world )| | | | +-------------- | | | | +--------------------+ WHAT DECORATOR DOES creates a new function (ID: 101) +---------------------------------+ |input arg: function with id: 100 | | | |print( before ) | |call function object with id 100 | |print( after ) | | | +---------------------------^-----+ | | AFTER | | | hello_world +-------------+ Note how the hello_world name points to a new function object but that new function object knows the reference (ID) of the original function. Some Gotchas While it is very quick to build prototypes in python and there are tons of libraries available, as the codebase complexity increases, type errors become more common and will get hard to deal with. (There are solutions to that problem like type annotations in python. Checkout mypy .) Because python is dynamically typed language, that means all types are determined at runtime. And that makes python run very slow compared to other statically typed languages. Python has something called GIL (global interpreter lock) which is a limiting factor for utilizing multiple CPI cores for parallel computation. Some weird things that python does: https://github.com/satwikkansal/wtfpython","title":"Some Python Concepts"},{"location":"python_web/python-concepts/#some-python-concepts","text":"Though you are expected to know python and its syntax at basic level, let us discuss some fundamental concepts that will help you understand the python language better. Everything in Python is an object. That includes the functions, lists, dicts, classes, modules, a running function (instance of function definition), everything. In the CPython, it would mean there is an underlying struct variable for each object. In python's current execution context, all the variables are stored in a dict. It'd be a string to object mapping. If you have a function and a float variable defined in the current context, here is how it is handled internally. float_number=42.0 def foo_func(): ... pass ... # NOTICE HOW VARIABLE NAMES ARE STRINGS, stored in a dict locals() {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': class '_frozen_importlib.BuiltinImporter' , '__spec__': None, '__annotations__': {}, '__builtins__': module 'builtins' (built-in) , 'float_number': 42.0, 'foo_func': function foo_func at 0x1055847a0 }","title":"Some Python Concepts"},{"location":"python_web/python-concepts/#python-functions","text":"Since functions too are objects, we can see what all attributes a function contains as following def hello(name): ... print(f Hello, {name}! ) ... dir(hello) ['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__'] While there are a lot of them, let's look at some interesting ones","title":"Python Functions"},{"location":"python_web/python-concepts/#globals","text":"This attribute, as the name suggests, has references of global variables. If you ever need to know what all global variables are in the scope of this function, this will tell you. See how the function start seeing the new variable in globals hello.__globals__ {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': class '_frozen_importlib.BuiltinImporter' , '__spec__': None, '__annotations__': {}, '__builtins__': module 'builtins' (built-in) , 'hello': function hello at 0x7fe4e82554c0 } # adding new global variable GLOBAL= g_val hello.__globals__ {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': class '_frozen_importlib.BuiltinImporter' , '__spec__': None, '__annotations__': {}, '__builtins__': module 'builtins' (built-in) , 'hello': function hello at 0x7fe4e82554c0 , 'GLOBAL': 'g_val'}","title":"globals"},{"location":"python_web/python-concepts/#code","text":"This is an interesting one! As everything in python is an object, this includes the bytecode too. The compiled python bytecode is a python code object. Which is accessible via __code__ attribute here. A function has an associated code object which carries some interesting information. # the file in which function is defined # stdin here since this is run in an interpreter hello.__code__.co_filename ' stdin ' # number of arguments the function takes hello.__code__.co_argcount 1 # local variable names hello.__code__.co_varnames ('name',) # the function code's compiled bytecode hello.__code__.co_code b't\\x00d\\x01|\\x00\\x9b\\x00d\\x02\\x9d\\x03\\x83\\x01\\x01\\x00d\\x00S\\x00' There are more code attributes which you can enlist by dir(hello.__code__)","title":"code"},{"location":"python_web/python-concepts/#decorators","text":"Related to functions, python has another feature called decorators. Let's see how that works, keeping everything is an object in mind. Here is a sample decorator: def deco(func): ... def inner(): ... print( before ) ... func() ... print( after ) ... return inner ... @deco ... def hello_world(): ... print( hello world ) ... hello_world() before hello world after Here @deco syntax is used to decorate the hello_world function. It is essentially same as doing def hello_world(): ... print( hello world ) ... hello_world = deco(hello_world) What goes inside the deco function might seem complex. Let's try to uncover it. Function hello_world is created It is passed to deco function deco create a new function This new function is calls hello_world function And does a couple other things deco returns the newly created function hello_world is replaced with above function Let's visualize it for better understanding BEFORE function_object (ID: 100) hello_world +--------------------+ + |print( hello_world )| | | | +-------------- | | | | +--------------------+ WHAT DECORATOR DOES creates a new function (ID: 101) +---------------------------------+ |input arg: function with id: 100 | | | |print( before ) | |call function object with id 100 | |print( after ) | | | +---------------------------^-----+ | | AFTER | | | hello_world +-------------+ Note how the hello_world name points to a new function object but that new function object knows the reference (ID) of the original function.","title":"Decorators"},{"location":"python_web/python-concepts/#some-gotchas","text":"While it is very quick to build prototypes in python and there are tons of libraries available, as the codebase complexity increases, type errors become more common and will get hard to deal with. (There are solutions to that problem like type annotations in python. Checkout mypy .) Because python is dynamically typed language, that means all types are determined at runtime. And that makes python run very slow compared to other statically typed languages. Python has something called GIL (global interpreter lock) which is a limiting factor for utilizing multiple CPI cores for parallel computation. Some weird things that python does: https://github.com/satwikkansal/wtfpython","title":"Some Gotchas"},{"location":"python_web/python-web-flask/","text":"Python, Web amd Flask Back in the old days, websites were simple. They were simple static html contents. A webserver would be listening on a defined port and according to the HTTP request received, it would read files from disk and return them in response. But since then, complexity has evolved and websites are now dynamic. Depending on the request, multiple operations need to be performed like reading from database or calling other API and finally returning some response (HTML data, JSON content etc.) Since serving web requests is no longer a simple task like reading files from disk and return contents, we need to process each http request, perform some operations programmatically and construct a response. Sockets Though we have frameworks like flask, HTTP is still a protocol that works over TCP protocol. So let us setup a TCP server and send an HTTP request and inspect the request's payload. Note that this is not a tutorial on socket programming but what we are doing here is inspecting HTTP protocol at its ground level and look at what its contents look like. (Ref: Socket Programming in Python (Guide) on RealPython ) import socket HOST = '127.0.0.1' # Standard loopback interface address (localhost) PORT = 65432 # Port to listen on (non-privileged ports are 1023) with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.bind((HOST, PORT)) s.listen() conn, addr = s.accept() with conn: print('Connected by', addr) while True: data = conn.recv(1024) if not data: break print(data) Then we open localhost:65432 in our web browser and following would be the output: Connected by ('127.0.0.1', 54719) b'GET / HTTP/1.1\\r\\nHost: localhost:65432\\r\\nConnection: keep-alive\\r\\nDNT: 1\\r\\nUpgrade-Insecure-Requests: 1\\r\\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36 Edg/85.0.564.44\\r\\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\\r\\nSec-Fetch-Site: none\\r\\nSec-Fetch-Mode: navigate\\r\\nSec-Fetch-User: ?1\\r\\nSec-Fetch-Dest: document\\r\\nAccept-Encoding: gzip, deflate, br\\r\\nAccept-Language: en-US,en;q=0.9\\r\\n\\r\\n' Examine closely and the content will look like the HTTP protocol's format. ie: HTTP_METHOD URI_PATH HTTP_VERSION HEADERS_SEPARATED_BY_SEPARATOR So though it's a blob of bytes, knowing http protocol specification , you can parse that string (ie: split by \\r\\n ) and get meaningful information out of it. Flask Flask, and other such frameworks does pretty much what we just discussed in the last section (with added more sophistication). They listen on a port on a TCP socket, receive an HTTP request, parse the data according to protocol format and make it available to you in a convenient manner. ie: you can access headers in flask by request.headers which is made available to you by splitting above payload by /r/n , as defined in http protocol. Another example: we register routes in flask by @app.route(\"/hello\") . What flask will do is maintain a registry internally which will map /hello with the function you decorated with. Now whenever a request comes with the /hello route (second component in the first line, split by space), flask calls the registered function and returns whatever the function returned. Same with all other web frameworks in other languages too. They all work on similar principles. What they basically do is understand the HTTP protocol, parses the HTTP request data and gives us programmers a nice interface to work with HTTP requests. Not so much of magic, innit?","title":"Python, Web and Flask"},{"location":"python_web/python-web-flask/#python-web-amd-flask","text":"Back in the old days, websites were simple. They were simple static html contents. A webserver would be listening on a defined port and according to the HTTP request received, it would read files from disk and return them in response. But since then, complexity has evolved and websites are now dynamic. Depending on the request, multiple operations need to be performed like reading from database or calling other API and finally returning some response (HTML data, JSON content etc.) Since serving web requests is no longer a simple task like reading files from disk and return contents, we need to process each http request, perform some operations programmatically and construct a response.","title":"Python, Web amd Flask"},{"location":"python_web/python-web-flask/#sockets","text":"Though we have frameworks like flask, HTTP is still a protocol that works over TCP protocol. So let us setup a TCP server and send an HTTP request and inspect the request's payload. Note that this is not a tutorial on socket programming but what we are doing here is inspecting HTTP protocol at its ground level and look at what its contents look like. (Ref: Socket Programming in Python (Guide) on RealPython ) import socket HOST = '127.0.0.1' # Standard loopback interface address (localhost) PORT = 65432 # Port to listen on (non-privileged ports are 1023) with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.bind((HOST, PORT)) s.listen() conn, addr = s.accept() with conn: print('Connected by', addr) while True: data = conn.recv(1024) if not data: break print(data) Then we open localhost:65432 in our web browser and following would be the output: Connected by ('127.0.0.1', 54719) b'GET / HTTP/1.1\\r\\nHost: localhost:65432\\r\\nConnection: keep-alive\\r\\nDNT: 1\\r\\nUpgrade-Insecure-Requests: 1\\r\\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36 Edg/85.0.564.44\\r\\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\\r\\nSec-Fetch-Site: none\\r\\nSec-Fetch-Mode: navigate\\r\\nSec-Fetch-User: ?1\\r\\nSec-Fetch-Dest: document\\r\\nAccept-Encoding: gzip, deflate, br\\r\\nAccept-Language: en-US,en;q=0.9\\r\\n\\r\\n' Examine closely and the content will look like the HTTP protocol's format. ie: HTTP_METHOD URI_PATH HTTP_VERSION HEADERS_SEPARATED_BY_SEPARATOR So though it's a blob of bytes, knowing http protocol specification , you can parse that string (ie: split by \\r\\n ) and get meaningful information out of it.","title":"Sockets"},{"location":"python_web/python-web-flask/#flask","text":"Flask, and other such frameworks does pretty much what we just discussed in the last section (with added more sophistication). They listen on a port on a TCP socket, receive an HTTP request, parse the data according to protocol format and make it available to you in a convenient manner. ie: you can access headers in flask by request.headers which is made available to you by splitting above payload by /r/n , as defined in http protocol. Another example: we register routes in flask by @app.route(\"/hello\") . What flask will do is maintain a registry internally which will map /hello with the function you decorated with. Now whenever a request comes with the /hello route (second component in the first line, split by space), flask calls the registered function and returns whatever the function returned. Same with all other web frameworks in other languages too. They all work on similar principles. What they basically do is understand the HTTP protocol, parses the HTTP request data and gives us programmers a nice interface to work with HTTP requests. Not so much of magic, innit?","title":"Flask"},{"location":"python_web/sre-conclusion/","text":"SRE Parts of The App and Conclusion Scaling The App The design and development is just a part of the journey. We will need to setup continuous integration and continuous delivery pipelines sooner or later. And we have to deploy this app somewhere. Initially we can start with deploying this app on one virtual machine on any cloud provider. But this is a Single point of failure which is something we never allow as an SRE (or even as an engineer). So an improvement here can be having multiple instances of applications deployed behind a load balancer. This certainly prevents problems of one machine going down. Scaling here would mean adding more instances behind the load balancer. But this is scalable upto only a certain point. After that, other bottlenecks in the system will start appearing. ie: DB will become the bottleneck, or perhaps the load balancer itself. How do you know what is the bottleneck? You need to have observability into each aspects of the application architecture. Only after you have metrics, you will be able to know what is going wrong where. What gets measured, gets fixed! Get deeper insights into scaling from School Of SRE's Scalability module and post going through it, apply your learnings and takeaways to this app. Think how will we make this app geographically distributed and highly available and scalable. Monitoring Strategy Once we have our application deployed. It will be working ok. But not forever. Reliability is in the title of our job and we make systems reliable by making the design in a certain way. But things still will go down. Machines will fail. Disks will behave weirdly. Buggy code will get pushed to production. And all these possible scenarios will make the system less reliable. So what do we do? We monitor! We keep an eye on the system's health and if anything is not going as expected, we want ourselves to get alerted. Now let's think in terms of the given url shortening app. We need to monitor it. And we would want to get notified in case something goes wrong. But we first need to decide what is that something that we want to keep an eye on. Since it's a web app serving HTTP requests, we want to keep an eye on HTTP Status codes and latencies Request volume again is a good candidate, if the app is receiving an unusual amount of traffic, something might be off. We also want to keep an eye on the database so depending on the database solution chosen. Query times, volumes, disk usage etc. Finally, there also needs to be some external monitoring which runs periodic tests from devices outside of your data centers. This emulates customers and ensures that from customer point of view, the system is working as expected. SRE Use-cases In the world of SRE, python is a widely used language. For small scripts and tooling developed for various purposes. Since tooling developed by SRE works with critical pieces of infrastructure and has great power (to bring things down), it is important to know what you are doing while using a programming language and its features. Also it is equally important to know the language and its characteristics while debugging the issues. As an SRE having a deeper understanding of python language, it has helped me a lot to debug very sneaky bugs and be generally more aware and informed while making certain design decisions. While developing tools may or may not be part of SRE job, supporting tools or services is more likely to be a daily duty. Building an application or tool is just a small part of productionization. While there is certainly that goes in the design of the application itself to make it more robust, as an SRE you are responsible for its reliability and stability once it is deployed and running. And to ensure that, you\u2019d need to understand the application first and then come up with a strategy to monitor it properly and be prepared for various failure scenarios. Optional Exercises Make a decorator that will cache function return values depending on input parameters. Host the URL shortening app on any cloud provider. Setup monitoring using many of the tools available like catchpoint, datadog etc. Create a minimal flask-like framework on top of TCP sockets. Conclusion This module, in the first part, aims to make you more aware of the things that will happen when you choose python as your programming language and what happens when you run a python program. With the knowledge of how python handles things internally as objects, lot of seemingly magic things in python will start to make more sense. The second part will first explain how a framework like flask works using the existing knowledge of protocols like TCP and HTTP. It then touches the whole lifecycle of an application development lifecycle including the SRE parts of it. While the design and areas in architecture considered will not be exhaustive, it will give a good overview of things that are also important being an SRE and why they are important.","title":"SRE Aspects of The App and Conclusion"},{"location":"python_web/sre-conclusion/#sre-parts-of-the-app-and-conclusion","text":"","title":"SRE Parts of The App and Conclusion"},{"location":"python_web/sre-conclusion/#scaling-the-app","text":"The design and development is just a part of the journey. We will need to setup continuous integration and continuous delivery pipelines sooner or later. And we have to deploy this app somewhere. Initially we can start with deploying this app on one virtual machine on any cloud provider. But this is a Single point of failure which is something we never allow as an SRE (or even as an engineer). So an improvement here can be having multiple instances of applications deployed behind a load balancer. This certainly prevents problems of one machine going down. Scaling here would mean adding more instances behind the load balancer. But this is scalable upto only a certain point. After that, other bottlenecks in the system will start appearing. ie: DB will become the bottleneck, or perhaps the load balancer itself. How do you know what is the bottleneck? You need to have observability into each aspects of the application architecture. Only after you have metrics, you will be able to know what is going wrong where. What gets measured, gets fixed! Get deeper insights into scaling from School Of SRE's Scalability module and post going through it, apply your learnings and takeaways to this app. Think how will we make this app geographically distributed and highly available and scalable.","title":"Scaling The App"},{"location":"python_web/sre-conclusion/#monitoring-strategy","text":"Once we have our application deployed. It will be working ok. But not forever. Reliability is in the title of our job and we make systems reliable by making the design in a certain way. But things still will go down. Machines will fail. Disks will behave weirdly. Buggy code will get pushed to production. And all these possible scenarios will make the system less reliable. So what do we do? We monitor! We keep an eye on the system's health and if anything is not going as expected, we want ourselves to get alerted. Now let's think in terms of the given url shortening app. We need to monitor it. And we would want to get notified in case something goes wrong. But we first need to decide what is that something that we want to keep an eye on. Since it's a web app serving HTTP requests, we want to keep an eye on HTTP Status codes and latencies Request volume again is a good candidate, if the app is receiving an unusual amount of traffic, something might be off. We also want to keep an eye on the database so depending on the database solution chosen. Query times, volumes, disk usage etc. Finally, there also needs to be some external monitoring which runs periodic tests from devices outside of your data centers. This emulates customers and ensures that from customer point of view, the system is working as expected.","title":"Monitoring Strategy"},{"location":"python_web/sre-conclusion/#sre-use-cases","text":"In the world of SRE, python is a widely used language. For small scripts and tooling developed for various purposes. Since tooling developed by SRE works with critical pieces of infrastructure and has great power (to bring things down), it is important to know what you are doing while using a programming language and its features. Also it is equally important to know the language and its characteristics while debugging the issues. As an SRE having a deeper understanding of python language, it has helped me a lot to debug very sneaky bugs and be generally more aware and informed while making certain design decisions. While developing tools may or may not be part of SRE job, supporting tools or services is more likely to be a daily duty. Building an application or tool is just a small part of productionization. While there is certainly that goes in the design of the application itself to make it more robust, as an SRE you are responsible for its reliability and stability once it is deployed and running. And to ensure that, you\u2019d need to understand the application first and then come up with a strategy to monitor it properly and be prepared for various failure scenarios.","title":"SRE Use-cases"},{"location":"python_web/sre-conclusion/#optional-exercises","text":"Make a decorator that will cache function return values depending on input parameters. Host the URL shortening app on any cloud provider. Setup monitoring using many of the tools available like catchpoint, datadog etc. Create a minimal flask-like framework on top of TCP sockets.","title":"Optional Exercises"},{"location":"python_web/sre-conclusion/#conclusion","text":"This module, in the first part, aims to make you more aware of the things that will happen when you choose python as your programming language and what happens when you run a python program. With the knowledge of how python handles things internally as objects, lot of seemingly magic things in python will start to make more sense. The second part will first explain how a framework like flask works using the existing knowledge of protocols like TCP and HTTP. It then touches the whole lifecycle of an application development lifecycle including the SRE parts of it. While the design and areas in architecture considered will not be exhaustive, it will give a good overview of things that are also important being an SRE and why they are important.","title":"Conclusion"},{"location":"python_web/url-shorten-app/","text":"The URL Shortening App Let's build a very simple URL shortening app using flask and try to incorporate all aspects of the development process including the reliability aspects. We will not be building the UI and we will come up with a minimal set of API that will be enough for the app to function well. Design We don't jump directly to coding. First thing we do is gather requirements. Come up with an approach. Have the approach/design reviewed by peers. Evolve, iterate, document the decisions and tradeoffs. And then finally implement. While we will not do the full blown design document here, we will raise certain questions here that are important to the design. 1. High Level Operations and API Endpoints Since it's a URL shortening app, we will need an API for generating the shorten link given an original link. And an API/Endpoint which will accept the shorten link and redirect to original URL. We are not including the user aspect of the app to keep things minimal. These two API should make app functional and usable by anyone. 2. How to shorten? Given a url, we will need to generate a shortened version of it. One approach could be using random characters for each link. Another thing that can be done is to use some sort of hashing algorithm. The benefit here is we will reuse the same hash for the same link. ie: if lot of people are shortening https://www.linkedin.com they all will have the same value, compared to multiple entries in DB if chosen random characters. What about hash collisions? Even in random characters approach, though there is a less probability, hash collisions can happen. And we need to be mindful of them. In that case we might want to prepend/append the string with some random value to avoid conflict. Also, choice of hash algorithm matters. We will need to analyze algorithms. Their CPU requirements and their characteristics. Choose one that suits the most. 3. Is URL Valid? Given a URL to shorten, how do we verify if the URL is valid? Do we even verify or validate? One basic check that can be done is see if the URL matches a regex of a URL. To go even further we can try opening/visiting the URL. But there are certain gotchas here. We need to define success criteria. ie: HTTP 200 means it is valid. What is the URL is in private network? What if URL is temporarily down? 4. Storage Finally, storage. Where will we store the data that we will generate over time? There are multiple database solutions available and we will need to choose the one that suits this app the most. Relational database like MySQL would be a fair choice but be sure to checkout School of SRE's database section for deeper insights into making a more informed decision. 5. Other We are not accounting for users into our app and other possible features like rate limiting, customized links etc but it will eventually come up with time. Depending on the requirements, they too might need to get incorporated. The minimal working code is given below for reference but I'd encourage you to come up with your own. from flask import Flask, redirect, request from hashlib import md5 app = Flask( url_shortener ) mapping = {} @app.route( /shorten , methods=[ POST ]) def shorten(): global mapping payload = request.json if url not in payload: return Missing URL Parameter , 400 # TODO: check if URL is valid hash_ = md5() hash_.update(payload[ url ].encode()) digest = hash_.hexdigest()[:5] # limiting to 5 chars. Less the limit more the chances of collission if digest not in mapping: mapping[digest] = payload[ url ] return f Shortened: r/{digest}\\n else: # TODO: check for hash collission return f Already exists: r/{digest}\\n @app.route( /r/ hash_ ) def redirect_(hash_): if hash_ not in mapping: return URL Not Found , 404 return redirect(mapping[hash_]) if __name__ == __main__ : app.run(debug=True) OUTPUT: === SHORTENING spatel1-mn1:tmp spatel1$ curl localhost:5000/shorten -H content-type: application/json --data '{ url : https://linkedin.com }' Shortened: r/a62a4 === REDIRECTING, notice the response code 302 and the location header spatel1-mn1:tmp spatel1$ curl localhost:5000/r/a62a4 -v * Uses proxy env variable NO_PROXY == '127.0.0.1' * Trying ::1... * TCP_NODELAY set * Connection failed * connect to ::1 port 5000 failed: Connection refused * Trying 127.0.0.1... * TCP_NODELAY set * Connected to localhost (127.0.0.1) port 5000 (#0) GET /r/a62a4 HTTP/1.1 Host: localhost:5000 User-Agent: curl/7.64.1 Accept: */* * HTTP 1.0, assume close after body HTTP/1.0 302 FOUND Content-Type: text/html; charset=utf-8 Content-Length: 247 Location: https://linkedin.com Server: Werkzeug/0.15.4 Python/3.7.7 Date: Tue, 27 Oct 2020 09:37:12 GMT !DOCTYPE HTML PUBLIC -//W3C//DTD HTML 3.2 Final//EN title Redirecting... /title h1 Redirecting... /h1 * Closing connection 0 p You should be redirected automatically to target URL: a href= https://linkedin.com https://linkedin.com /a . If not click the link.","title":"The URL Shortening App"},{"location":"python_web/url-shorten-app/#the-url-shortening-app","text":"Let's build a very simple URL shortening app using flask and try to incorporate all aspects of the development process including the reliability aspects. We will not be building the UI and we will come up with a minimal set of API that will be enough for the app to function well.","title":"The URL Shortening App"},{"location":"python_web/url-shorten-app/#design","text":"We don't jump directly to coding. First thing we do is gather requirements. Come up with an approach. Have the approach/design reviewed by peers. Evolve, iterate, document the decisions and tradeoffs. And then finally implement. While we will not do the full blown design document here, we will raise certain questions here that are important to the design.","title":"Design"},{"location":"python_web/url-shorten-app/#1-high-level-operations-and-api-endpoints","text":"Since it's a URL shortening app, we will need an API for generating the shorten link given an original link. And an API/Endpoint which will accept the shorten link and redirect to original URL. We are not including the user aspect of the app to keep things minimal. These two API should make app functional and usable by anyone.","title":"1. High Level Operations and API Endpoints"},{"location":"python_web/url-shorten-app/#2-how-to-shorten","text":"Given a url, we will need to generate a shortened version of it. One approach could be using random characters for each link. Another thing that can be done is to use some sort of hashing algorithm. The benefit here is we will reuse the same hash for the same link. ie: if lot of people are shortening https://www.linkedin.com they all will have the same value, compared to multiple entries in DB if chosen random characters. What about hash collisions? Even in random characters approach, though there is a less probability, hash collisions can happen. And we need to be mindful of them. In that case we might want to prepend/append the string with some random value to avoid conflict. Also, choice of hash algorithm matters. We will need to analyze algorithms. Their CPU requirements and their characteristics. Choose one that suits the most.","title":"2. How to shorten?"},{"location":"python_web/url-shorten-app/#3-is-url-valid","text":"Given a URL to shorten, how do we verify if the URL is valid? Do we even verify or validate? One basic check that can be done is see if the URL matches a regex of a URL. To go even further we can try opening/visiting the URL. But there are certain gotchas here. We need to define success criteria. ie: HTTP 200 means it is valid. What is the URL is in private network? What if URL is temporarily down?","title":"3. Is URL Valid?"},{"location":"python_web/url-shorten-app/#4-storage","text":"Finally, storage. Where will we store the data that we will generate over time? There are multiple database solutions available and we will need to choose the one that suits this app the most. Relational database like MySQL would be a fair choice but be sure to checkout School of SRE's database section for deeper insights into making a more informed decision.","title":"4. Storage"},{"location":"python_web/url-shorten-app/#5-other","text":"We are not accounting for users into our app and other possible features like rate limiting, customized links etc but it will eventually come up with time. Depending on the requirements, they too might need to get incorporated. The minimal working code is given below for reference but I'd encourage you to come up with your own. from flask import Flask, redirect, request from hashlib import md5 app = Flask( url_shortener ) mapping = {} @app.route( /shorten , methods=[ POST ]) def shorten(): global mapping payload = request.json if url not in payload: return Missing URL Parameter , 400 # TODO: check if URL is valid hash_ = md5() hash_.update(payload[ url ].encode()) digest = hash_.hexdigest()[:5] # limiting to 5 chars. Less the limit more the chances of collission if digest not in mapping: mapping[digest] = payload[ url ] return f Shortened: r/{digest}\\n else: # TODO: check for hash collission return f Already exists: r/{digest}\\n @app.route( /r/ hash_ ) def redirect_(hash_): if hash_ not in mapping: return URL Not Found , 404 return redirect(mapping[hash_]) if __name__ == __main__ : app.run(debug=True) OUTPUT: === SHORTENING spatel1-mn1:tmp spatel1$ curl localhost:5000/shorten -H content-type: application/json --data '{ url : https://linkedin.com }' Shortened: r/a62a4 === REDIRECTING, notice the response code 302 and the location header spatel1-mn1:tmp spatel1$ curl localhost:5000/r/a62a4 -v * Uses proxy env variable NO_PROXY == '127.0.0.1' * Trying ::1... * TCP_NODELAY set * Connection failed * connect to ::1 port 5000 failed: Connection refused * Trying 127.0.0.1... * TCP_NODELAY set * Connected to localhost (127.0.0.1) port 5000 (#0) GET /r/a62a4 HTTP/1.1 Host: localhost:5000 User-Agent: curl/7.64.1 Accept: */* * HTTP 1.0, assume close after body HTTP/1.0 302 FOUND Content-Type: text/html; charset=utf-8 Content-Length: 247 Location: https://linkedin.com Server: Werkzeug/0.15.4 Python/3.7.7 Date: Tue, 27 Oct 2020 09:37:12 GMT !DOCTYPE HTML PUBLIC -//W3C//DTD HTML 3.2 Final//EN title Redirecting... /title h1 Redirecting... /h1 * Closing connection 0 p You should be redirected automatically to target URL: a href= https://linkedin.com https://linkedin.com /a . If not click the link.","title":"5. Other"},{"location":"systems_design/availability/","text":"HA - Availability - Common \u201cNines\u201d Availability is generally expressed as \u201cNines\u201d, common \u2018Nines\u2019 are listed below. Availability % Downtime per year Downtime per month Downtime per week Downtime per day 99%(Two Nines) 3.65 days 7.31 hours 1.68 hours 14.40 minutes 99.5%(Two and a half Nines) 1.83 days 3.65 hours 50.40 minutes 7.20 minutes 99.9%(Three Nines) 8.77 hours 43.83 minutes 10.08 minutes 1.44 minutes 99.95%(Three and a half Nines) 4.38 hours 21.92 minutes 5.04 minutes 43.20 seconds 99.99%(Four Nines) 52.60 minutes 4.38 minutes 1.01 minutes 8.64 seconds 99.995%(Four and a half Nines) 26.30 minutes 2.19 minutes 30.24 seconds 4.32 seconds 99.999%(Five Nines) 5.26 minutes 26.30 seconds 6.05 seconds 864.0 ms Refer https://en.wikipedia.org/wiki/High_availability#Percentage_calculation HA - Availability Serial Components A System with components is operating in the series If failure of a part leads to the combination becoming inoperable. For example if LB in our architecture fails, all access to app tiers will fail. LB and app tiers are connected serially. The combined availability of the system is the product of individual components availability A = Ax x Ay x \u2026.. Refer http://www.eventhelix.com/RealtimeMantra/FaultHandling/system_reliability_availability.htm HA - Availability Parallel Components A System with components is operating in parallel If failure of a part leads to the other part taking over the operations of the failed part. If we have more than one LB and if rest of the LBs can take over the traffic during one LB failure then LBs are operating in parallel The combined availability of the system is A = 1 - ( (1-Ax) x (1-Ax) x \u2026.. ) Refer http://www.eventhelix.com/RealtimeMantra/FaultHandling/system_reliability_availability.htm HA - Core Principles Elimination of single points of failure (SPOF) This means adding redundancy to the system so that the failure of a component does not mean failure of the entire system. Reliable crossover In redundant systems, the crossover point itself tends to become a single point of failure. Reliable systems must provide for reliable crossover. Detection of failures as they occur If the two principles above are observed, then a user may never see a failure Refer https://en.wikipedia.org/wiki/High_availability#Principles HA - SPOF WHAT: Never implement and always eliminate single points of failure. WHEN TO USE: During architecture reviews and new designs. HOW TO USE: Identify single instances on architectural diagrams. Strive for active/active configurations. At the very least we should have a standby to take control when active instances fail. WHY: Maximize availability through multiple instances. KEY TAKEAWAYS: Strive for active/active rather than active/passive solutions. Use load balancers to balance traffic across instances of a service. Use control services with active/passive instances for patterns that require singletons. HA - Reliable Crossover WHAT: Ensure when system components failover they do so reliably. WHEN TO USE: During architecture reviews, failure modeling, and designs. HOW TO USE: Identify how available a system is during the crossover and ensure it is within acceptable limits. WHY: Maximize availability and ensure data handling semantics are preserved. KEY TAKEAWAYS: Strive for active/active rather than active/passive solutions, they have a lesser risk of cross over being unreliable. Use LB and right load balancing methods to ensure reliable failover. Model and build your data systems to ensure data is correctly handled when crossover happens. Generally DB systems follow active/passive semantics for writes. Masters accept writes and when master goes down, follower is promoted to master(active from being passive) to accept writes. We have to be careful here that the cutover never introduces more than one masters. This problem is called a split brain. SRE Use cases SRE works on deciding an acceptable SLA and make sure system is available to achieve the SLA SRE is involved in architecture design right from building the data center to make sure site is not affected by network switch, hardware, power or software failures SRE also run mock drills of failures to see how the system behaves in uncharted territory and comes up with a plan to improve availability if there are misses. https://engineering.linkedin.com/blog/2017/11/resilience-engineering-at-linkedin-with-project-waterbear Post our understanding about HA, our architecture diagram looks something like this below","title":"Availability"},{"location":"systems_design/availability/#ha-availability-common-nines","text":"Availability is generally expressed as \u201cNines\u201d, common \u2018Nines\u2019 are listed below. Availability % Downtime per year Downtime per month Downtime per week Downtime per day 99%(Two Nines) 3.65 days 7.31 hours 1.68 hours 14.40 minutes 99.5%(Two and a half Nines) 1.83 days 3.65 hours 50.40 minutes 7.20 minutes 99.9%(Three Nines) 8.77 hours 43.83 minutes 10.08 minutes 1.44 minutes 99.95%(Three and a half Nines) 4.38 hours 21.92 minutes 5.04 minutes 43.20 seconds 99.99%(Four Nines) 52.60 minutes 4.38 minutes 1.01 minutes 8.64 seconds 99.995%(Four and a half Nines) 26.30 minutes 2.19 minutes 30.24 seconds 4.32 seconds 99.999%(Five Nines) 5.26 minutes 26.30 seconds 6.05 seconds 864.0 ms","title":"HA - Availability - Common \u201cNines\u201d"},{"location":"systems_design/availability/#refer","text":"https://en.wikipedia.org/wiki/High_availability#Percentage_calculation","title":"Refer"},{"location":"systems_design/availability/#ha-availability-serial-components","text":"A System with components is operating in the series If failure of a part leads to the combination becoming inoperable. For example if LB in our architecture fails, all access to app tiers will fail. LB and app tiers are connected serially. The combined availability of the system is the product of individual components availability A = Ax x Ay x \u2026..","title":"HA - Availability Serial Components"},{"location":"systems_design/availability/#refer_1","text":"http://www.eventhelix.com/RealtimeMantra/FaultHandling/system_reliability_availability.htm","title":"Refer"},{"location":"systems_design/availability/#ha-availability-parallel-components","text":"A System with components is operating in parallel If failure of a part leads to the other part taking over the operations of the failed part. If we have more than one LB and if rest of the LBs can take over the traffic during one LB failure then LBs are operating in parallel The combined availability of the system is A = 1 - ( (1-Ax) x (1-Ax) x \u2026.. )","title":"HA - Availability Parallel Components"},{"location":"systems_design/availability/#refer_2","text":"http://www.eventhelix.com/RealtimeMantra/FaultHandling/system_reliability_availability.htm","title":"Refer"},{"location":"systems_design/availability/#ha-core-principles","text":"Elimination of single points of failure (SPOF) This means adding redundancy to the system so that the failure of a component does not mean failure of the entire system. Reliable crossover In redundant systems, the crossover point itself tends to become a single point of failure. Reliable systems must provide for reliable crossover. Detection of failures as they occur If the two principles above are observed, then a user may never see a failure","title":"HA - Core Principles"},{"location":"systems_design/availability/#refer_3","text":"https://en.wikipedia.org/wiki/High_availability#Principles","title":"Refer"},{"location":"systems_design/availability/#ha-spof","text":"WHAT: Never implement and always eliminate single points of failure. WHEN TO USE: During architecture reviews and new designs. HOW TO USE: Identify single instances on architectural diagrams. Strive for active/active configurations. At the very least we should have a standby to take control when active instances fail. WHY: Maximize availability through multiple instances. KEY TAKEAWAYS: Strive for active/active rather than active/passive solutions. Use load balancers to balance traffic across instances of a service. Use control services with active/passive instances for patterns that require singletons.","title":"HA - SPOF"},{"location":"systems_design/availability/#ha-reliable-crossover","text":"WHAT: Ensure when system components failover they do so reliably. WHEN TO USE: During architecture reviews, failure modeling, and designs. HOW TO USE: Identify how available a system is during the crossover and ensure it is within acceptable limits. WHY: Maximize availability and ensure data handling semantics are preserved. KEY TAKEAWAYS: Strive for active/active rather than active/passive solutions, they have a lesser risk of cross over being unreliable. Use LB and right load balancing methods to ensure reliable failover. Model and build your data systems to ensure data is correctly handled when crossover happens. Generally DB systems follow active/passive semantics for writes. Masters accept writes and when master goes down, follower is promoted to master(active from being passive) to accept writes. We have to be careful here that the cutover never introduces more than one masters. This problem is called a split brain.","title":"HA - Reliable Crossover"},{"location":"systems_design/availability/#sre-use-cases","text":"SRE works on deciding an acceptable SLA and make sure system is available to achieve the SLA SRE is involved in architecture design right from building the data center to make sure site is not affected by network switch, hardware, power or software failures SRE also run mock drills of failures to see how the system behaves in uncharted territory and comes up with a plan to improve availability if there are misses. https://engineering.linkedin.com/blog/2017/11/resilience-engineering-at-linkedin-with-project-waterbear Post our understanding about HA, our architecture diagram looks something like this below","title":"SRE Use cases"},{"location":"systems_design/conclusion/","text":"Conclusion Armed with these principles, we hope the course will give a fresh perspective to design software systems. It might be over engineering to get all this on day zero. But some are really important from day 0 like eliminating single points of failure, making scalable services by just increasing replicas. As a bottleneck is reached, we can split code by services, shard data to scale. As the organisation matures, bringing in chaos engineering to measure how systems react to failure will help in designing robust software systems.","title":"Conclusion"},{"location":"systems_design/conclusion/#conclusion","text":"Armed with these principles, we hope the course will give a fresh perspective to design software systems. It might be over engineering to get all this on day zero. But some are really important from day 0 like eliminating single points of failure, making scalable services by just increasing replicas. As a bottleneck is reached, we can split code by services, shard data to scale. As the organisation matures, bringing in chaos engineering to measure how systems react to failure will help in designing robust software systems.","title":"Conclusion"},{"location":"systems_design/fault-tolerance/","text":"Fault Tolerance Failures are not avoidable in any system and will happen all the time, hence we need to build systems that can tolerate failures or recover from them. In systems, failure is the norm rather than the exception. \"Anything that can go wrong will go wrong\u201d -- Murphy\u2019s Law \u201cComplex systems contain changing mixtures of failures latent within them\u201d -- How Complex Systems Fail. Fault Tolerance - Failure Metrics Common failure metrics that get measured and tracked for any system. Mean time to repair (MTTR): The average time to repair and restore a failed system. Mean time between failures (MTBF): The average operational time between one device failure or system breakdown and the next. Mean time to failure (MTTF): The average time a device or system is expected to function before it fails. Mean time to detect (MTTD): The average time between the onset of a problem and when the organization detects it. Mean time to investigate (MTTI): The average time between the detection of an incident and when the organization begins to investigate its cause and solution. Mean time to restore service (MTRS): The average elapsed time from the detection of an incident until the affected system or component is again available to users. Mean time between system incidents (MTBSI): The average elapsed time between the detection of two consecutive incidents. MTBSI can be calculated by adding MTBF and MTRS (MTBSI = MTBF + MTRS). Failure rate: Another reliability metric, which measures the frequency with which a component or system fails. It is expressed as a number of failures over a unit of time. Refer https://www.splunk.com/en_us/data-insider/what-is-mean-time-to-repair.html Fault Tolerance - Fault Isolation Terms Systems should have a short circuit. Say in our content sharing system, if \u201cNotifications\u201d is not working, the site should gracefully handle that failure by removing the functionality instead of taking the whole site down. Swimlane is one of the commonly used fault isolation methodology. Swimlane adds a barrier to the service from other services so that failure on either of them won\u2019t affect the other. Say we roll out a new feature \u2018Advertisement\u2019 in our content sharing app. We can have two architectures If Ads are generated on the fly synchronously during each Newsfeed request, the faults in Ads feature gets propagated to Newsfeed feature. Instead if we swimlane \u201cGeneration of Ads\u201d service and use a shared storage to populate Newsfeed App, Ads failures won\u2019t cascade to Newsfeed and worst case if Ads don\u2019t meet SLA , we can have Newsfeed without Ads. Let's take another example, we come up with a new model for our Content sharing App. Here we roll out enterprise content sharing App where enterprises pay for the service and the content should never be shared outside the enterprise. Swimlane Principles Principle 1: Nothing is shared (also known as \u201cshare as little as possible\u201d). The less that is shared within a swim lane, the more fault isolative the swim lane becomes. (as shown in Enterprise usecase) Principle 2: Nothing crosses a swim lane boundary. Synchronous (defined by expecting a request\u2014not the transfer protocol) communication never crosses a swim lane boundary; if it does, the boundary is drawn incorrectly. (as shown in Ads feature) Swimlane Approaches Approach 1: Swim lane the money-maker. Never allow your cash register to be compromised by other systems. (Tier 1 vs Tier 2 in enterprise use case) Approach 2: Swim lane the biggest sources of incidents. Identify the recurring causes of pain and isolate them.(if Ads feature is in code yellow, swim laning it is the best option) Approach 3: Swim lane natural barriers. Customer boundaries make good swim lanes.(Public vs Enterprise customers) Refer https://learning.oreilly.com/library/view/the-art-of/9780134031408/ch21.html#ch21 SRE Use cases: Work with the DC tech or cloud team to distribute infrastructure such that its immune to switch or power failures by creating fault zones within a Data Center https://docs.microsoft.com/en-us/azure/virtual-machines/manage-availability#use-availability-zones-to-protect-from-datacenter-level-failures Work with the partners and design interaction between services such that one service breakdown is not amplified in a cascading fashion to all upstreams","title":"Fault Tolerance"},{"location":"systems_design/fault-tolerance/#fault-tolerance","text":"Failures are not avoidable in any system and will happen all the time, hence we need to build systems that can tolerate failures or recover from them. In systems, failure is the norm rather than the exception. \"Anything that can go wrong will go wrong\u201d -- Murphy\u2019s Law \u201cComplex systems contain changing mixtures of failures latent within them\u201d -- How Complex Systems Fail.","title":"Fault Tolerance"},{"location":"systems_design/fault-tolerance/#fault-tolerance-failure-metrics","text":"Common failure metrics that get measured and tracked for any system. Mean time to repair (MTTR): The average time to repair and restore a failed system. Mean time between failures (MTBF): The average operational time between one device failure or system breakdown and the next. Mean time to failure (MTTF): The average time a device or system is expected to function before it fails. Mean time to detect (MTTD): The average time between the onset of a problem and when the organization detects it. Mean time to investigate (MTTI): The average time between the detection of an incident and when the organization begins to investigate its cause and solution. Mean time to restore service (MTRS): The average elapsed time from the detection of an incident until the affected system or component is again available to users. Mean time between system incidents (MTBSI): The average elapsed time between the detection of two consecutive incidents. MTBSI can be calculated by adding MTBF and MTRS (MTBSI = MTBF + MTRS). Failure rate: Another reliability metric, which measures the frequency with which a component or system fails. It is expressed as a number of failures over a unit of time.","title":"Fault Tolerance - Failure Metrics"},{"location":"systems_design/fault-tolerance/#refer","text":"https://www.splunk.com/en_us/data-insider/what-is-mean-time-to-repair.html","title":"Refer"},{"location":"systems_design/fault-tolerance/#fault-tolerance-fault-isolation-terms","text":"Systems should have a short circuit. Say in our content sharing system, if \u201cNotifications\u201d is not working, the site should gracefully handle that failure by removing the functionality instead of taking the whole site down. Swimlane is one of the commonly used fault isolation methodology. Swimlane adds a barrier to the service from other services so that failure on either of them won\u2019t affect the other. Say we roll out a new feature \u2018Advertisement\u2019 in our content sharing app. We can have two architectures If Ads are generated on the fly synchronously during each Newsfeed request, the faults in Ads feature gets propagated to Newsfeed feature. Instead if we swimlane \u201cGeneration of Ads\u201d service and use a shared storage to populate Newsfeed App, Ads failures won\u2019t cascade to Newsfeed and worst case if Ads don\u2019t meet SLA , we can have Newsfeed without Ads. Let's take another example, we come up with a new model for our Content sharing App. Here we roll out enterprise content sharing App where enterprises pay for the service and the content should never be shared outside the enterprise.","title":"Fault Tolerance - Fault Isolation Terms"},{"location":"systems_design/fault-tolerance/#swimlane-principles","text":"Principle 1: Nothing is shared (also known as \u201cshare as little as possible\u201d). The less that is shared within a swim lane, the more fault isolative the swim lane becomes. (as shown in Enterprise usecase) Principle 2: Nothing crosses a swim lane boundary. Synchronous (defined by expecting a request\u2014not the transfer protocol) communication never crosses a swim lane boundary; if it does, the boundary is drawn incorrectly. (as shown in Ads feature)","title":"Swimlane Principles"},{"location":"systems_design/fault-tolerance/#swimlane-approaches","text":"Approach 1: Swim lane the money-maker. Never allow your cash register to be compromised by other systems. (Tier 1 vs Tier 2 in enterprise use case) Approach 2: Swim lane the biggest sources of incidents. Identify the recurring causes of pain and isolate them.(if Ads feature is in code yellow, swim laning it is the best option) Approach 3: Swim lane natural barriers. Customer boundaries make good swim lanes.(Public vs Enterprise customers)","title":"Swimlane Approaches"},{"location":"systems_design/fault-tolerance/#refer_1","text":"https://learning.oreilly.com/library/view/the-art-of/9780134031408/ch21.html#ch21","title":"Refer"},{"location":"systems_design/fault-tolerance/#sre-use-cases","text":"Work with the DC tech or cloud team to distribute infrastructure such that its immune to switch or power failures by creating fault zones within a Data Center https://docs.microsoft.com/en-us/azure/virtual-machines/manage-availability#use-availability-zones-to-protect-from-datacenter-level-failures Work with the partners and design interaction between services such that one service breakdown is not amplified in a cascading fashion to all upstreams","title":"SRE Use cases:"},{"location":"systems_design/intro/","text":"Systems Design Pre - Requisites Fundamentals of common software system components: - Operating Systems - Networking - Databases RDBMS/NoSQL What to expect from this training Thinking about and designing for scalability, availability, and reliability of large scale software systems. What is not covered under this training Individual software components\u2019 scalability and reliability concerns like e.g. Databases, while the same scalability principles and thinking can be applied, these individual components have their own specific nuances when scaling them and thinking about their reliability. More light will be shed on concepts rather than on setting up and configuring components like Loadbalancers to achieve scalability, availability and reliability of systems Training Content Introduction Scalability High Availability Fault Tolerance Introduction So, how do you go about learning to design a system? \u201d Like most great questions, it showed a level of naivety that was breathtaking. The only short answer I could give was, essentially, that you learned how to design a system by designing systems and finding out what works and what doesn\u2019t work.\u201d Jim Waldo, Sun Microsystems, On System Design As software and hardware systems have multiple moving parts, we need to think about how those parts will grow, their failure modes, their inter-dependencies, how it will impact the users and the business. There is no one-shot method or way to learn or do system design, we only learn to design systems by designing and iterating on them. This course will be a starter to make one think about scalability, availability, and fault tolerance during systems design. Backstory Let\u2019s design a simple content sharing application where users can share photos, media in our application which can be liked by their friends. Let\u2019s start with a simple design of the application and evolve it as we learn system design concepts","title":"Intro"},{"location":"systems_design/intro/#systems-design","text":"","title":"Systems Design"},{"location":"systems_design/intro/#pre-requisites","text":"Fundamentals of common software system components: - Operating Systems - Networking - Databases RDBMS/NoSQL","title":"Pre - Requisites"},{"location":"systems_design/intro/#what-to-expect-from-this-training","text":"Thinking about and designing for scalability, availability, and reliability of large scale software systems.","title":"What to expect from this training"},{"location":"systems_design/intro/#what-is-not-covered-under-this-training","text":"Individual software components\u2019 scalability and reliability concerns like e.g. Databases, while the same scalability principles and thinking can be applied, these individual components have their own specific nuances when scaling them and thinking about their reliability. More light will be shed on concepts rather than on setting up and configuring components like Loadbalancers to achieve scalability, availability and reliability of systems","title":"What is not covered under this training"},{"location":"systems_design/intro/#training-content","text":"Introduction Scalability High Availability Fault Tolerance","title":"Training Content"},{"location":"systems_design/intro/#introduction","text":"So, how do you go about learning to design a system? \u201d Like most great questions, it showed a level of naivety that was breathtaking. The only short answer I could give was, essentially, that you learned how to design a system by designing systems and finding out what works and what doesn\u2019t work.\u201d Jim Waldo, Sun Microsystems, On System Design As software and hardware systems have multiple moving parts, we need to think about how those parts will grow, their failure modes, their inter-dependencies, how it will impact the users and the business. There is no one-shot method or way to learn or do system design, we only learn to design systems by designing and iterating on them. This course will be a starter to make one think about scalability, availability, and fault tolerance during systems design.","title":"Introduction"},{"location":"systems_design/intro/#backstory","text":"Let\u2019s design a simple content sharing application where users can share photos, media in our application which can be liked by their friends. Let\u2019s start with a simple design of the application and evolve it as we learn system design concepts","title":"Backstory"},{"location":"systems_design/scalability/","text":"Scalability What does scalability mean for a system/service? A system is composed of services/components, each service/component scalability needs to be tackled separately, and the scalability of the system as a whole. A service is said to be scalable if, as resources are added to the system, it results in increased performance in a manner proportional to resources added An always-on service is said to be scalable if adding resources to facilitate redundancy does not result in a loss of performance Refer https://www.allthingsdistributed.com/2006/03/a_word_on_scalability.html Scalability - AKF Scale Cube The Scale Cube is a model for segmenting services, defining microservices, and scaling products. It also creates a common language for teams to discuss scale related options in designing solutions. Following section talks about certain scaling patterns based on our inferences from AKF cube Scalability - Horizontal scaling Horizontal scaling stands for cloning of an application or service such that work can easily be distributed across instances with absolutely no bias. Lets see how our monolithic application improves with this principle Here DB is scaled separately from the application. This is to let you know each component\u2019s scaling capabilities can be different. Usually web applications can be scaled by adding resources unless there is no state stored inside the application. But DBs can be scaled only for Reads by adding more followers but Writes have to go to only one master to make sure data is consistent. There are some DBs which support multi master writes but we are keeping them out of scope at this point. Apps should be able to differentiate between Read and Writes to choose appropriate DB servers. Load balancers can split traffic between identical servers transparently. WHAT: Duplication of services or databases to spread transaction load. WHEN TO USE: Databases with a very high read-to-write ratio (5:1 or greater\u2014the higher the better). Because only read replicas of DBs can be scaled, not the Master. HOW TO USE: Simply clone services and implement a load balancer. For databases, ensure that the accessing code understands the difference between a read and a write. WHY: Allows for fast scale of transactions at the cost of duplicated data and functionality. KEY TAKEAWAYS: This is fast to implement, is low cost from a developer effort perspective, and can scale transaction volumes nicely. However, they tend to be high cost from the perspective of the operational cost of data. Cost here means if we have 3 followers and 1 Master DB, the same database will be stored as 4 copies in the 4 servers. Hence added storage cost Refer https://learning.oreilly.com/library/view/the-art-of/9780134031408/ch23.html Scalability Pattern - Load Balancing Improves the distribution of workloads across multiple computing resources, such as computers, a computer cluster, network links, central processing units, or disk drives. Commonly used technique is load balancing traffic across identical server clusters. Similar philosophy is used to load balance traffic across network links by ECMP , disk drives by RAID etc Aims to optimize resource use, maximize throughput, minimize response time, and avoid overload of any single resource. Using multiple components with load balancing instead of a single component may increase reliability and availability through redundancy. In our updated architecture diagram we have 4 servers to handle app traffic instead of a single server The device or system that performs load balancing is called a load balancer, abbreviated as LB. Refer https://en.wikipedia.org/wiki/Load_balancing_(computing) https://blog.envoyproxy.io/introduction-to-modern-network-load-balancing-and-proxying-a57f6ff80236 https://learning.oreilly.com/library/view/load-balancing-in/9781492038009/ https://learning.oreilly.com/library/view/practical-load-balancing/9781430236801/ http://shop.oreilly.com/product/9780596000509.do Scalability Pattern - LB Tasks What does an LB do? Service discovery: What backends are available in the system? In our architecture, 4 servers are available to serve App traffic. LB acts as a single endpoint that clients can use transparently to reach one of the 4 servers. Health checking: What backends are currently healthy and available to accept requests? If one out of the 4 App servers turns bad, LB should automatically short circuit the path so that clients don\u2019t sense any application downtime Load balancing: What algorithm should be used to balance individual requests across the healthy backends? There are many algorithms to distribute traffic across one of the four servers. Based on observations/experience, SRE can pick the algorithm that suits their pattern Scalability Pattern - LB Methods Common Load Balancing Methods Least Connection Method directs traffic to the server with the fewest active connections. Most useful when there are a large number of persistent connections in the traffic unevenly distributed between the servers. Works if clients maintain long lived connections Least Response Time Method directs traffic to the server with the fewest active connections and the lowest average response time. Here response time is used to provide feedback of server\u2019s health Round Robin Method rotates servers by directing traffic to the first available server and then moves that server to the bottom of the queue. Most useful when servers are of equal specification and there are not many persistent connections. IP Hash the IP address of the client determines which server receives the request. This can sometimes cause skewness in distribution but is useful if apps store some state locally and need some stickiness More advanced client/server-side example techniques - https://docs.nginx.com/nginx/admin-guide/load-balancer/ - http://cbonte.github.io/haproxy-dconv/2.2/intro.html#3.3.5 - https://twitter.github.io/finagle/guide/Clients.html#load-balancing Scalability Pattern - Caching - Content Delivery Networks (CDN) CDNs are added closer to the client\u2019s location. If the app has static data like images, Javascript, CSS which don\u2019t change very often, they can be cached. Since our example is a content sharing site, static content can be cached in CDNs with a suitable expiry. WHAT: Use CDNs (content delivery networks) to offload traffic from your site. WHEN TO USE: When speed improvements and scale warrant the additional cost. HOW TO USE: Most CDNs leverage DNS to serve content on your site\u2019s behalf. Thus you may need to make minor DNS changes or additions and move content to be served from new subdomains. Eg media-exp1.licdn.com is a domain used by Linkedin to serve static content Here a CNAME points the domain to the DNS of CDN provider dig media-exp1.licdn.com +short 2-01-2c3e-005c.cdx.cedexis.net. WHY: CDNs help offload traffic spikes and are often economical ways to scale parts of a site\u2019s traffic. They also often substantially improve page download times. KEY TAKEAWAYS: CDNs are a fast and simple way to offset the spikiness of traffic as well as traffic growth in general. Make sure you perform a cost-benefit analysis and monitor the CDN usage. If CDNs have a lot of cache misses, then we don\u2019t gain much from CDN and are still serving requests using our compute resources. Scalability - Microservices This pattern represents the separation of work by service or function within the application. Microservices are meant to address the issues associated with growth and complexity in the code base and data sets. The intent is to create fault isolation as well as to reduce response times. Microservices can scale transactions, data sizes, and codebase sizes. They are most effective in scaling the size and complexity of your codebase. They tend to cost a bit more than horizontal scaling because the engineering team needs to rewrite services or, at the very least, disaggregate them from the original monolithic application. WHAT: Sometimes referred to as scale through services or resources, this rule focuses on scaling by splitting data sets, transactions, and engineering teams along verb (services) or noun (resources) boundaries. WHEN TO USE: Very large data sets where relations between data are not necessary. Large, complex systems where scaling engineering resources requires specialization. HOW TO USE: Split up actions by using verbs, or resources by using nouns, or use a mix. Split both the services and the data along the lines defined by the verb/noun approach. WHY: Allows for efficient scaling of not only transactions but also very large data sets associated with those transactions. It also allows for the efficient scaling of teams. KEY TAKEAWAYS: Microservices allow for efficient scaling of transactions, large data sets, and can help with fault isolation. It helps reduce the communication overhead of teams. The codebase becomes less complex as disjoint features are decoupled and spun as new services thereby letting each service scale independently specific to its requirement. Refer https://learning.oreilly.com/library/view/the-art-of/9780134031408/ch23.html Scalability - Sharding This pattern represents the separation of work based on attributes that are looked up or determined at the time of the transaction. Most often, these are implemented as splits by requestor, customer, or client. Very often, a lookup service or deterministic algorithm will need to be written for these types of splits. Sharding aids in scaling transaction growth, scaling instruction sets, and decreasing processing time (the last by limiting the data necessary to perform any transaction). This is more effective at scaling growth in customers or clients. It can aid with disaster recovery efforts, and limit the impact of incidents to only a specific segment of customers. Here the auth data is sharded based on user names so that DBs can respond faster as the amount of data DBs have to work on has drastically reduced during queries. There can be other ways to split Here the whole data centre is split and replicated and clients are directed to a data centre based on their geography. This helps in improving performance as clients are directed to the closest Data centre and performance increases as we add more data centres. There are some replication and consistency overhead with this approach one needs to be aware of. This also gives fault tolerance by rolling out test features to one site and rollback if there is an impact to that geography WHAT: This is very often a split by some unique aspect of the customer such as customer ID, name, geography, and so on. WHEN TO USE: Very large, similar data sets such as large and rapidly growing customer bases or when the response time for a geographically distributed customer base is important. HOW TO USE: Identify something you know about the customer, such as customer ID, last name, geography, or device, and split or partition both data and services based on that attribute. WHY: Rapid customer growth exceeds other forms of data growth, or you have the need to perform fault isolation between certain customer groups as you scale. KEY TAKEAWAYS: Shards are effective at helping you to scale customer bases but can also be applied to other very large data sets that can\u2019t be pulled apart using the microservices methodology. Refer https://learning.oreilly.com/library/view/the-art-of/9780134031408/ch23.html SRE Use cases SREs in coordination with the network team work on how to map users traffic to a particular site. https://engineering.linkedin.com/blog/2017/05/trafficshift--load-testing-at-scale SREs work closely with the Dev team to split monoliths to multiple microservices that are easy to run and manage SREs work on improving Load Balancers' reliability, service discovery and performance SREs work closely to split Data into shards and manage data integrity and consistency. https://engineering.linkedin.com/espresso/introducing-espresso-linkedins-hot-new-distributed-document-store SREs work to set up, configure and improve CDN cache hit rate.","title":"Scalability"},{"location":"systems_design/scalability/#scalability","text":"What does scalability mean for a system/service? A system is composed of services/components, each service/component scalability needs to be tackled separately, and the scalability of the system as a whole. A service is said to be scalable if, as resources are added to the system, it results in increased performance in a manner proportional to resources added An always-on service is said to be scalable if adding resources to facilitate redundancy does not result in a loss of performance","title":"Scalability"},{"location":"systems_design/scalability/#refer","text":"https://www.allthingsdistributed.com/2006/03/a_word_on_scalability.html","title":"Refer"},{"location":"systems_design/scalability/#scalability-akf-scale-cube","text":"The Scale Cube is a model for segmenting services, defining microservices, and scaling products. It also creates a common language for teams to discuss scale related options in designing solutions. Following section talks about certain scaling patterns based on our inferences from AKF cube","title":"Scalability - AKF Scale Cube"},{"location":"systems_design/scalability/#scalability-horizontal-scaling","text":"Horizontal scaling stands for cloning of an application or service such that work can easily be distributed across instances with absolutely no bias. Lets see how our monolithic application improves with this principle Here DB is scaled separately from the application. This is to let you know each component\u2019s scaling capabilities can be different. Usually web applications can be scaled by adding resources unless there is no state stored inside the application. But DBs can be scaled only for Reads by adding more followers but Writes have to go to only one master to make sure data is consistent. There are some DBs which support multi master writes but we are keeping them out of scope at this point. Apps should be able to differentiate between Read and Writes to choose appropriate DB servers. Load balancers can split traffic between identical servers transparently. WHAT: Duplication of services or databases to spread transaction load. WHEN TO USE: Databases with a very high read-to-write ratio (5:1 or greater\u2014the higher the better). Because only read replicas of DBs can be scaled, not the Master. HOW TO USE: Simply clone services and implement a load balancer. For databases, ensure that the accessing code understands the difference between a read and a write. WHY: Allows for fast scale of transactions at the cost of duplicated data and functionality. KEY TAKEAWAYS: This is fast to implement, is low cost from a developer effort perspective, and can scale transaction volumes nicely. However, they tend to be high cost from the perspective of the operational cost of data. Cost here means if we have 3 followers and 1 Master DB, the same database will be stored as 4 copies in the 4 servers. Hence added storage cost","title":"Scalability - Horizontal scaling"},{"location":"systems_design/scalability/#refer_1","text":"https://learning.oreilly.com/library/view/the-art-of/9780134031408/ch23.html","title":"Refer"},{"location":"systems_design/scalability/#scalability-pattern-load-balancing","text":"Improves the distribution of workloads across multiple computing resources, such as computers, a computer cluster, network links, central processing units, or disk drives. Commonly used technique is load balancing traffic across identical server clusters. Similar philosophy is used to load balance traffic across network links by ECMP , disk drives by RAID etc Aims to optimize resource use, maximize throughput, minimize response time, and avoid overload of any single resource. Using multiple components with load balancing instead of a single component may increase reliability and availability through redundancy. In our updated architecture diagram we have 4 servers to handle app traffic instead of a single server The device or system that performs load balancing is called a load balancer, abbreviated as LB.","title":"Scalability Pattern - Load Balancing"},{"location":"systems_design/scalability/#refer_2","text":"https://en.wikipedia.org/wiki/Load_balancing_(computing) https://blog.envoyproxy.io/introduction-to-modern-network-load-balancing-and-proxying-a57f6ff80236 https://learning.oreilly.com/library/view/load-balancing-in/9781492038009/ https://learning.oreilly.com/library/view/practical-load-balancing/9781430236801/ http://shop.oreilly.com/product/9780596000509.do","title":"Refer"},{"location":"systems_design/scalability/#scalability-pattern-lb-tasks","text":"What does an LB do?","title":"Scalability Pattern - LB Tasks"},{"location":"systems_design/scalability/#service-discovery","text":"What backends are available in the system? In our architecture, 4 servers are available to serve App traffic. LB acts as a single endpoint that clients can use transparently to reach one of the 4 servers.","title":"Service discovery:"},{"location":"systems_design/scalability/#health-checking","text":"What backends are currently healthy and available to accept requests? If one out of the 4 App servers turns bad, LB should automatically short circuit the path so that clients don\u2019t sense any application downtime","title":"Health checking:"},{"location":"systems_design/scalability/#load-balancing","text":"What algorithm should be used to balance individual requests across the healthy backends? There are many algorithms to distribute traffic across one of the four servers. Based on observations/experience, SRE can pick the algorithm that suits their pattern","title":"Load balancing:"},{"location":"systems_design/scalability/#scalability-pattern-lb-methods","text":"Common Load Balancing Methods","title":"Scalability Pattern - LB Methods"},{"location":"systems_design/scalability/#least-connection-method","text":"directs traffic to the server with the fewest active connections. Most useful when there are a large number of persistent connections in the traffic unevenly distributed between the servers. Works if clients maintain long lived connections","title":"Least Connection Method"},{"location":"systems_design/scalability/#least-response-time-method","text":"directs traffic to the server with the fewest active connections and the lowest average response time. Here response time is used to provide feedback of server\u2019s health","title":"Least Response Time Method"},{"location":"systems_design/scalability/#round-robin-method","text":"rotates servers by directing traffic to the first available server and then moves that server to the bottom of the queue. Most useful when servers are of equal specification and there are not many persistent connections.","title":"Round Robin Method"},{"location":"systems_design/scalability/#ip-hash","text":"the IP address of the client determines which server receives the request. This can sometimes cause skewness in distribution but is useful if apps store some state locally and need some stickiness More advanced client/server-side example techniques - https://docs.nginx.com/nginx/admin-guide/load-balancer/ - http://cbonte.github.io/haproxy-dconv/2.2/intro.html#3.3.5 - https://twitter.github.io/finagle/guide/Clients.html#load-balancing","title":"IP Hash"},{"location":"systems_design/scalability/#scalability-pattern-caching-content-delivery-networks-cdn","text":"CDNs are added closer to the client\u2019s location. If the app has static data like images, Javascript, CSS which don\u2019t change very often, they can be cached. Since our example is a content sharing site, static content can be cached in CDNs with a suitable expiry. WHAT: Use CDNs (content delivery networks) to offload traffic from your site. WHEN TO USE: When speed improvements and scale warrant the additional cost. HOW TO USE: Most CDNs leverage DNS to serve content on your site\u2019s behalf. Thus you may need to make minor DNS changes or additions and move content to be served from new subdomains. Eg media-exp1.licdn.com is a domain used by Linkedin to serve static content Here a CNAME points the domain to the DNS of CDN provider dig media-exp1.licdn.com +short 2-01-2c3e-005c.cdx.cedexis.net. WHY: CDNs help offload traffic spikes and are often economical ways to scale parts of a site\u2019s traffic. They also often substantially improve page download times. KEY TAKEAWAYS: CDNs are a fast and simple way to offset the spikiness of traffic as well as traffic growth in general. Make sure you perform a cost-benefit analysis and monitor the CDN usage. If CDNs have a lot of cache misses, then we don\u2019t gain much from CDN and are still serving requests using our compute resources.","title":"Scalability Pattern - Caching - Content Delivery Networks (CDN)"},{"location":"systems_design/scalability/#scalability-microservices","text":"This pattern represents the separation of work by service or function within the application. Microservices are meant to address the issues associated with growth and complexity in the code base and data sets. The intent is to create fault isolation as well as to reduce response times. Microservices can scale transactions, data sizes, and codebase sizes. They are most effective in scaling the size and complexity of your codebase. They tend to cost a bit more than horizontal scaling because the engineering team needs to rewrite services or, at the very least, disaggregate them from the original monolithic application. WHAT: Sometimes referred to as scale through services or resources, this rule focuses on scaling by splitting data sets, transactions, and engineering teams along verb (services) or noun (resources) boundaries. WHEN TO USE: Very large data sets where relations between data are not necessary. Large, complex systems where scaling engineering resources requires specialization. HOW TO USE: Split up actions by using verbs, or resources by using nouns, or use a mix. Split both the services and the data along the lines defined by the verb/noun approach. WHY: Allows for efficient scaling of not only transactions but also very large data sets associated with those transactions. It also allows for the efficient scaling of teams. KEY TAKEAWAYS: Microservices allow for efficient scaling of transactions, large data sets, and can help with fault isolation. It helps reduce the communication overhead of teams. The codebase becomes less complex as disjoint features are decoupled and spun as new services thereby letting each service scale independently specific to its requirement.","title":"Scalability - Microservices"},{"location":"systems_design/scalability/#refer_3","text":"https://learning.oreilly.com/library/view/the-art-of/9780134031408/ch23.html","title":"Refer"},{"location":"systems_design/scalability/#scalability-sharding","text":"This pattern represents the separation of work based on attributes that are looked up or determined at the time of the transaction. Most often, these are implemented as splits by requestor, customer, or client. Very often, a lookup service or deterministic algorithm will need to be written for these types of splits. Sharding aids in scaling transaction growth, scaling instruction sets, and decreasing processing time (the last by limiting the data necessary to perform any transaction). This is more effective at scaling growth in customers or clients. It can aid with disaster recovery efforts, and limit the impact of incidents to only a specific segment of customers. Here the auth data is sharded based on user names so that DBs can respond faster as the amount of data DBs have to work on has drastically reduced during queries. There can be other ways to split Here the whole data centre is split and replicated and clients are directed to a data centre based on their geography. This helps in improving performance as clients are directed to the closest Data centre and performance increases as we add more data centres. There are some replication and consistency overhead with this approach one needs to be aware of. This also gives fault tolerance by rolling out test features to one site and rollback if there is an impact to that geography WHAT: This is very often a split by some unique aspect of the customer such as customer ID, name, geography, and so on. WHEN TO USE: Very large, similar data sets such as large and rapidly growing customer bases or when the response time for a geographically distributed customer base is important. HOW TO USE: Identify something you know about the customer, such as customer ID, last name, geography, or device, and split or partition both data and services based on that attribute. WHY: Rapid customer growth exceeds other forms of data growth, or you have the need to perform fault isolation between certain customer groups as you scale. KEY TAKEAWAYS: Shards are effective at helping you to scale customer bases but can also be applied to other very large data sets that can\u2019t be pulled apart using the microservices methodology.","title":"Scalability - Sharding"},{"location":"systems_design/scalability/#refer_4","text":"https://learning.oreilly.com/library/view/the-art-of/9780134031408/ch23.html","title":"Refer"},{"location":"systems_design/scalability/#sre-use-cases","text":"SREs in coordination with the network team work on how to map users traffic to a particular site. https://engineering.linkedin.com/blog/2017/05/trafficshift--load-testing-at-scale SREs work closely with the Dev team to split monoliths to multiple microservices that are easy to run and manage SREs work on improving Load Balancers' reliability, service discovery and performance SREs work closely to split Data into shards and manage data integrity and consistency. https://engineering.linkedin.com/espresso/introducing-espresso-linkedins-hot-new-distributed-document-store SREs work to set up, configure and improve CDN cache hit rate.","title":"SRE Use cases"}]} \ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml index 9dae467..452160b 100644 --- a/sitemap.xml +++ b/sitemap.xml @@ -2,72 +2,102 @@ None - 2020-11-03 + 2020-11-05 daily None - 2020-11-03 + 2020-11-05 daily None - 2020-11-03 + 2020-11-05 daily None - 2020-11-03 + 2020-11-05 daily None - 2020-11-03 + 2020-11-05 daily None - 2020-11-03 + 2020-11-05 daily None - 2020-11-03 + 2020-11-05 daily None - 2020-11-03 + 2020-11-05 daily None - 2020-11-03 + 2020-11-05 daily None - 2020-11-03 + 2020-11-05 daily None - 2020-11-03 + 2020-11-05 daily None - 2020-11-03 + 2020-11-05 daily None - 2020-11-03 + 2020-11-05 daily None - 2020-11-03 + 2020-11-05 + daily + + + None + 2020-11-05 + daily + + + None + 2020-11-05 + daily + + + None + 2020-11-05 + daily + + + None + 2020-11-05 + daily + + + None + 2020-11-05 + daily + + + None + 2020-11-05 daily \ No newline at end of file diff --git a/sitemap.xml.gz b/sitemap.xml.gz index b29426d20d6ba5e0cd079b28267e95c47c1a147e..6cfa9a9aab23ac1c65a7cd0b2d7d9a2f4255f895 100644 GIT binary patch delta 185 zcmV;q07n1J0nhqSC265@X!Y%-wi+M65tciCE`00pkwhSD4qK>j7s9xwbebjtCg?$qK%8$W zs4iZBt|4V)hed|snLk)M#Cxa2(OJ#u+ Conclusion + + + + diff --git a/systems_design/conclusion/index.html b/systems_design/conclusion/index.html index 768b9cb..0b46b07 100644 --- a/systems_design/conclusion/index.html +++ b/systems_design/conclusion/index.html @@ -113,6 +113,35 @@
  • Conclusion +
  • + + + @@ -129,8 +158,8 @@ Previous -
  • -
  • diff --git a/systems_design/fault-tolerance/index.html b/systems_design/fault-tolerance/index.html index 36a2dd9..30bdfb3 100644 --- a/systems_design/fault-tolerance/index.html +++ b/systems_design/fault-tolerance/index.html @@ -113,6 +113,35 @@
  • Conclusion +
  • + + + diff --git a/systems_design/intro/index.html b/systems_design/intro/index.html index 5f8a0c5..444298c 100644 --- a/systems_design/intro/index.html +++ b/systems_design/intro/index.html @@ -113,6 +113,35 @@
  • Conclusion +
  • + + + diff --git a/systems_design/scalability/index.html b/systems_design/scalability/index.html index e4f337b..d1852e5 100644 --- a/systems_design/scalability/index.html +++ b/systems_design/scalability/index.html @@ -113,6 +113,35 @@
  • Conclusion +
  • + + +