08 October 2016
Blocking users who have ad-blockers? You must be joking!
If we could simply trust people in this world, everything would be a lot simpler. Security checks everywhere wouldn't be necessary, roads wouldn't have road humps, cars wouldn't need alarms...and so on. However, that's not how the world works. There are always some people who spoil it for everyone else. Someone has to cheat, someone has to drive too fast, someone always has to steal.
And someone has to scream for attention by using flashy advertisements and popups when you are busy with some work, browsing the internet.
While we do love the people who came up with ad-blockers, I've come across some very silly websites which block the people who use ad-blockers. A very off-putting move. Why in the world would I visit your website again?
Here's one from the Times of India website:
Thenthere's there was a really sweet one from quickfever.com (Don't worry, it doesn't really matter if your site has a clean layout and minimal ads. If I disable my ad-blocker, I'll be hit with plenty of ads from the other websites I visit. So I won't be disabling it):
Update [27 Oct 2016]: As shown in the comments at the bottom of this post, quickfever.com no longer has the message. These guys are really cool!
The most ironic one was from Forbes.com. Their quote for the day says "You might have a great thing - but if you don't know how to market, then you can't succeed". So yes Forbes, you may have a great website but if you block users who have ad-blockers, then you can't succeed in convincing people to click on your ads anyway. Bye.
Dear blockers of ad-blockers,
You may have a great website with great content. You may need to generate money out of ad clicks. But that's not something that concerns us. Any amount of advertising is futile if the consumer does not want the product in the first place. The use of an ad-blocker is indicative of that. We won't be clicking your ads even if you allowed us to view your website with our ad-blockers disabled.
By putting up the patronizing "Ad blocker detected" message, you've not found a technological solution to make money. You've just found a nice way to annoy people and get your website an "I'll never visit that website again" reputation.
And someone has to scream for attention by using flashy advertisements and popups when you are busy with some work, browsing the internet.
While we do love the people who came up with ad-blockers, I've come across some very silly websites which block the people who use ad-blockers. A very off-putting move. Why in the world would I visit your website again?
Here's one from the Times of India website:
Then
Update [27 Oct 2016]: As shown in the comments at the bottom of this post, quickfever.com no longer has the message. These guys are really cool!
The most ironic one was from Forbes.com. Their quote for the day says "You might have a great thing - but if you don't know how to market, then you can't succeed". So yes Forbes, you may have a great website but if you block users who have ad-blockers, then you can't succeed in convincing people to click on your ads anyway. Bye.
Dear blockers of ad-blockers,
You may have a great website with great content. You may need to generate money out of ad clicks. But that's not something that concerns us. Any amount of advertising is futile if the consumer does not want the product in the first place. The use of an ad-blocker is indicative of that. We won't be clicking your ads even if you allowed us to view your website with our ad-blockers disabled.
By putting up the patronizing "Ad blocker detected" message, you've not found a technological solution to make money. You've just found a nice way to annoy people and get your website an "I'll never visit that website again" reputation.
The honesty of government officers
When most people talk of government officers, it's mostly with disappointment and mistrust.
I have however come across many officers who take pains to do their job honestly. Conversations with them have revealed how they dislike the way the 'system' functions and guess what? They say they would be more satisfied with their jobs if they were allowed to do their jobs honestly and efficiently.
Everyone makes mistakes, and even our Prime Minister had once remarked about honest officers in the government. I congratulate these officers who have had the courage and integrity to do their duty well.
I have however come across many officers who take pains to do their job honestly. Conversations with them have revealed how they dislike the way the 'system' functions and guess what? They say they would be more satisfied with their jobs if they were allowed to do their jobs honestly and efficiently.
Everyone makes mistakes, and even our Prime Minister had once remarked about honest officers in the government. I congratulate these officers who have had the courage and integrity to do their duty well.
18 September 2016
Concepts about Storm you need to know [Part 2]
There are some concepts that are always good to know when you are tearing your hair over the working of Storm.
Yaml correctness:
Storm's config files use yaml to define properties, so if you get an error like this when modifying zookeeper servers:
Yaml correctness:
Storm's config files use yaml to define properties, so if you get an error like this when modifying zookeeper servers:
Exception in thread "main" java.lang.ExceptionInInitializerError
at org.apache.storm.config$read_storm_config.invoke(config.clj:78)
at org.apache.storm.command.list$_main.invoke(list.clj:22)
at clojure.lang.AFn.applyToHelper(AFn.java:152)
at clojure.lang.AFn.applyTo(AFn.java:144)
at org.apache.storm.command.list.main(Unknown Source)
Caused by: while scanning a simple key
in 'reader', line 2, column 1:
storm.zookeeper.port:2181
^
could not found expected ':'
in 'reader', line 3, column 1:
nimbus.host: "localhost"
^
caused when
storm.zookeeper.servers: - "127.0.0.1"
nimbus.seeds: ["127.0.0.1"]
at org.apache.storm.config$read_storm_config.invoke(config.clj:78)
at org.apache.storm.command.list$_main.invoke(list.clj:22)
at clojure.lang.AFn.applyToHelper(AFn.java:152)
at clojure.lang.AFn.applyTo(AFn.java:144)
at org.apache.storm.command.list.main(Unknown Source)
Caused by: while scanning a simple key
in 'reader', line 2, column 1:
storm.zookeeper.port:2181
^
could not found expected ':'
in 'reader', line 3, column 1:
nimbus.host: "localhost"
^
caused when
storm.zookeeper.servers: - "127.0.0.1"
nimbus.seeds: ["127.0.0.1"]
It's because you didn't conform to the yaml syntax. You can validate it at http://www.yamllint.com/ or any other yaml validator website.
Logviewer:
When viewing Storm results from the Storm UI (especially from localhost), you are allowed to click on the port numbers (which are hyperlinks) and you'll be able to see the contents of the log files to which storm logs output.
This works only if you have started the logviewer. This is basically just a storm process you have to start.
Goto the storm bin folder and type ./storm logviewer
Storm's slots:
Storm, by default provides 4 "slots" on each node that you run it on. Why only 4 ports?
A slot is used by a worker. A worker is a JVM. So each JVM would require a clump of heap memory of its own. So a default of 4 slots would use 4*x amount of memory, where x is the memory used by a worker JVM.
Now obviously if you declare more than 4 ports, it'll take up that much more memory. The problem with taking up too much memory, is that your topologies will suddenly crash with a GC overhead limit exceeded exception and the spout or bolt will get re-started constantly. As I understand, you'd be better off with increasing the number of servers or RAM on the existing server, if you want to have many workers/topologies.
Setting the amount of memory used by a topology:
From what I know, you can figure out how much memory your application needs and allocate the memory as shown below.
The below code allocates 2GiB of memory for each worker of the topology.
Config stormConfig = new Config(); stormConfig.put(Config.TOPOLOGY_WORKER_CHILDOPTS, "-Xmx2g");
Fields grouping does not get overwhelmed with data:
We would think that the way Storm keeps track of unique fields for fields grouping is by having a HashMap of the fields and bolt Id's. That's not how it works.
This is the internal Storm code for fields grouping:
public List chooseTasks(int taskId, List
TupleUtils.listHashCode leads to
public static int listHashCode(List alist) {
if (alist == null) {
return 1;
} else {
return Arrays.deepHashCode(alist.toArray());
}
}
So the function is independent of which Spout emits it. What matters is the field name based on which the fields grouping is done.
More help:
Logviewer:
When viewing Storm results from the Storm UI (especially from localhost), you are allowed to click on the port numbers (which are hyperlinks) and you'll be able to see the contents of the log files to which storm logs output.
This works only if you have started the logviewer. This is basically just a storm process you have to start.
Goto the storm bin folder and type ./storm logviewer
Storm's slots:
Storm, by default provides 4 "slots" on each node that you run it on. Why only 4 ports?
A slot is used by a worker. A worker is a JVM. So each JVM would require a clump of heap memory of its own. So a default of 4 slots would use 4*x amount of memory, where x is the memory used by a worker JVM.
Now obviously if you declare more than 4 ports, it'll take up that much more memory. The problem with taking up too much memory, is that your topologies will suddenly crash with a GC overhead limit exceeded exception and the spout or bolt will get re-started constantly. As I understand, you'd be better off with increasing the number of servers or RAM on the existing server, if you want to have many workers/topologies.
Setting the amount of memory used by a topology:
From what I know, you can figure out how much memory your application needs and allocate the memory as shown below.
The below code allocates 2GiB of memory for each worker of the topology.
Config stormConfig = new Config(); stormConfig.put(Config.TOPOLOGY_WORKER_CHILDOPTS, "-Xmx2g");
Fields grouping does not get overwhelmed with data:
We would think that the way Storm keeps track of unique fields for fields grouping is by having a HashMap of the fields and bolt Id's. That's not how it works.
This is the internal Storm code for fields grouping:
public List
TupleUtils.listHashCode leads to
public static
if (alist == null) {
return 1;
} else {
return Arrays.deepHashCode(alist.toArray());
}
}
So the function is independent of which Spout emits it. What matters is the field name based on which the fields grouping is done.
More help:
Understanding Fields Grouping in Apache Storm [Part 3]
Fields grouping can be confusing to understand from the examples given on the internet because they all have some strings in the declaration fields which just don't make any sense, given that we are supposed to send tuples based on some "fields".
So basically the concept is this:
But that does not have to be the case.
You can even send other values as the fields of the tuple and specify one of those fields as the unique ID that Storm should use for doing fields grouping.
A "field" by definition is: "Data encapsulated in a class or object".
So when you specify fields for a fields grouping, you are first declaring to Storm that you are creating a Tuple object which has fields which you want to create, and you give the fields some names you choose:
ofd.declare(new Fields(FieldName1, FieldName2, FieldName3));
This is pretty much (although not exactly) like declaring an object like this (pseudocode):
object Tuple {
{FieldName1: Array[0]};
{FieldName2: Array[1]};
{FieldName3: Array[2]};
}
And then when emitting, you specify the values that you want to store in those array positions:
collector.emit(new Values(tup, randNum, spoutId), tup);
object Tuple {
{FieldName1: Array[0] = new MyData();};
{FieldName2: Array[1] = 50.46334};
{FieldName3: Array[2] = 67};
}
You can give any String name to FieldName1, 2 and 3. You can also store any object in those array positions.
Now if you want to tell Storm to use one of those field names to do the Tuple grouping, you simply specify the FieldName when declaring the topology. Done like this:
.fieldsGrouping(spoutName, new Fields(FieldName2));
That's it. It's that simple. Follow the text in red below.
BasicStorm.java
package com.sdint.basicstorm;
import org.apache.storm.Config;
import org.apache.storm.LocalCluster;
import org.apache.storm.topology.TopologyBuilder;
import org.apache.storm.tuple.Fields;
public class BasicStorm {
public static void main(String[] cmdArgs) {
Config config = new Config();
config.put(Config.TOPOLOGY_DEBUG, false);
config.put(Config.TOPOLOGY_MAX_SPOUT_PENDING, 1);
config.put(Config.TOPOLOGY_MESSAGE_TIMEOUT_SECS, 10);//alters the default 30 second of tuple timeout to 10 second
final String spoutName = DataSpout.class.getSimpleName();
final String boltName = ProcessingBolt.class.getSimpleName();
final Integer numberOfSpouts = 1;
final Integer numberOfBolts = 5;
String someRandomNumber = "someRandomNumber";
String theSpoutId = "theSpoutId";
TopologyBuilder builder = new TopologyBuilder();
builder.setSpout(spoutName, new DataSpout(), numberOfSpouts)
.setNumTasks(numberOfSpouts);
builder.setBolt(boltName, new ProcessingBolt(), numberOfBolts)
.setNumTasks(numberOfBolts)
.fieldsGrouping(spoutName, new Fields(someRandomNumber));
LocalCluster localCluster = new LocalCluster();
localCluster.submitTopology("BasicStorm", config, builder.createTopology());
System.out.println("\n\n\nTopology submitted\n\n\n");
StopWatch.pause(60*3);//pause for x seconds during which the emitting of tuples will happen
//localCluster.killTopology("BasicStorm");
localCluster.shutdown();
}
}
DataSpout.java
package com.sdint.basicstorm;
import java.util.Map;
import org.apache.storm.spout.SpoutOutputCollector;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.topology.base.BaseRichSpout;
import org.apache.storm.tuple.Fields;
import org.apache.storm.tuple.Values;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DataSpout extends BaseRichSpout {
private TopologyContext context;
private SpoutOutputCollector collector;
private transient Integer spoutId;
private transient Integer numberOfEmits;
private final Logger logger = LoggerFactory.getLogger(DataSpout.class);
@Override
public void open(Map map, TopologyContext tc, SpoutOutputCollector soc) {
this.context = tc;
this.collector = soc;
this.spoutId = context.getThisTaskId();
this.numberOfEmits = 0;
}
@Override
public void declareOutputFields(OutputFieldsDeclarer ofd) {
String someRandomNumber = "someRandomNumber";//these fields strings have to be exactly the same wherever you specify them
String theSpoutId = "theSpoutId";
String aTupleName = "aTupleName";
ofd.declare(new Fields(aTupleName, someRandomNumber, theSpoutId));
}
@Override
public void nextTuple() {
if (++numberOfEmits < 200) {
Double randNum = Math.floor(Math.random() * 10);
SomeTuple tup = new SomeTuple();
collector.emit(new Values(tup, randNum, spoutId), tup);//(tuple, uniqueID for FieldsGrouping, spoutID), unique messageId
logger.info("Emitting {}",randNum);
}
else {logger.info("NO MORE EMITS");}
StopWatch.pauseMilli(300);
}
@Override
public void ack(Object msgId) {
}
@Override
public void fail(Object msgId) {
}
}
ProcessingBolt.java
package com.sdint.basicstorm;
import java.util.HashMap;
import java.util.Map;
import org.apache.storm.task.OutputCollector;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.topology.base.BaseRichBolt;
import org.apache.storm.tuple.Tuple;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ProcessingBolt extends BaseRichBolt {
private OutputCollector collector;
private transient Integer boltId;
private final Logger logger = LoggerFactory.getLogger(ProcessingBolt.class);
private HashMap counter;
@Override
public void declareOutputFields(OutputFieldsDeclarer ofd) {
}
@Override
public void prepare(Map map, TopologyContext tc, OutputCollector oc) {
collector = oc;
boltId = tc.getThisTaskId();
counter = new HashMap<>();
}
@Override
public void execute(Tuple tuple) {
//From the spout, Values(randNum, spoutId) were sent, so we receive it here
SomeTuple tup = (SomeTuple) tuple.getValue(0);//the first value in the fields
Double num = (Double) tuple.getValue(1);//the second value sent in the fields
Integer spoutId = (Integer) tuple.getValue(2);//the third value sent in the fields
//Similarly, you could have sent more values from the spout and received it as tuple.getValue(3) or (4) etc.
logger.info("Bolt {} received {} from spout {}", boltId, num, spoutId);
CountTheNumberOfSimilarNumbersReceived(num);
DisplayCounts();
collector.ack(tuple);
}
private void CountTheNumberOfSimilarNumbersReceived(final Double num) {
if (counter.containsKey(num)) {
Integer existingCount = counter.get(num);
counter.put(num, ++existingCount);//increment the count for this number
} else {
counter.put(num, 0);
}
}
private void DisplayCounts() {
logger.info("\n\n\n\nBolt {} has", boltId);
for (Map.Entry oneVal : counter.entrySet()) {
logger.info("{} number of {}", oneVal.getValue(), oneVal.getKey());
}
logger.info("\n\n\n");
}
}
So if random number is 1.0, the tuple might be sent to bolt 5.
If random number is 2.0, the tuple might be sent to bolt 3.
If random number is 2.0 again, the tuple will be sent to bolt 3.
That's all there is to it. If you want, you can even have the random number as the tuple. It could also be a String instead of a number. Any object you want. You can also re-order the fields in the tuple. Just make sure you also have the same order in the declareOutputFields function.
You also need to know that fields grouping does not overwhelm Storm with data.
More on Apache Storm:
So basically the concept is this:
- In shuffle grouping, you send any tuple you want from a spout and Storm will decide which of the numerous bolt tasks it should reach.
- In fields grouping, you can send the same tuple from the spout and also specify a unique ID based on which Storm can for example, send all tuples with id1 always to bolt3 or send tuples with id4 always to bolt 5 or id6 always to bolt5. Multiple id's can go to the same bolt, but they will continue being sent to the same bolt.
But that does not have to be the case.
You can even send other values as the fields of the tuple and specify one of those fields as the unique ID that Storm should use for doing fields grouping.
A "field" by definition is: "Data encapsulated in a class or object".
So when you specify fields for a fields grouping, you are first declaring to Storm that you are creating a Tuple object which has fields which you want to create, and you give the fields some names you choose:
ofd.declare(new Fields(FieldName1, FieldName2, FieldName3));
This is pretty much (although not exactly) like declaring an object like this (pseudocode):
object Tuple {
{FieldName1: Array[0]};
{FieldName2: Array[1]};
{FieldName3: Array[2]};
}
And then when emitting, you specify the values that you want to store in those array positions:
collector.emit(new Values(tup, randNum, spoutId), tup);
object Tuple {
{FieldName1: Array[0] = new MyData();};
{FieldName2: Array[1] = 50.46334};
{FieldName3: Array[2] = 67};
}
You can give any String name to FieldName1, 2 and 3. You can also store any object in those array positions.
Now if you want to tell Storm to use one of those field names to do the Tuple grouping, you simply specify the FieldName when declaring the topology. Done like this:
.fieldsGrouping(spoutName, new Fields(FieldName2));
That's it. It's that simple. Follow the text in red below.
BasicStorm.java
package com.sdint.basicstorm;
import org.apache.storm.Config;
import org.apache.storm.LocalCluster;
import org.apache.storm.topology.TopologyBuilder;
import org.apache.storm.tuple.Fields;
public class BasicStorm {
public static void main(String[] cmdArgs) {
Config config = new Config();
config.put(Config.TOPOLOGY_DEBUG, false);
config.put(Config.TOPOLOGY_MAX_SPOUT_PENDING, 1);
config.put(Config.TOPOLOGY_MESSAGE_TIMEOUT_SECS, 10);//alters the default 30 second of tuple timeout to 10 second
final String spoutName = DataSpout.class.getSimpleName();
final String boltName = ProcessingBolt.class.getSimpleName();
final Integer numberOfSpouts = 1;
final Integer numberOfBolts = 5;
String someRandomNumber = "someRandomNumber";
String theSpoutId = "theSpoutId";
TopologyBuilder builder = new TopologyBuilder();
builder.setSpout(spoutName, new DataSpout(), numberOfSpouts)
.setNumTasks(numberOfSpouts);
builder.setBolt(boltName, new ProcessingBolt(), numberOfBolts)
.setNumTasks(numberOfBolts)
.fieldsGrouping(spoutName, new Fields(someRandomNumber));
LocalCluster localCluster = new LocalCluster();
localCluster.submitTopology("BasicStorm", config, builder.createTopology());
System.out.println("\n\n\nTopology submitted\n\n\n");
StopWatch.pause(60*3);//pause for x seconds during which the emitting of tuples will happen
//localCluster.killTopology("BasicStorm");
localCluster.shutdown();
}
}
DataSpout.java
package com.sdint.basicstorm;
import java.util.Map;
import org.apache.storm.spout.SpoutOutputCollector;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.topology.base.BaseRichSpout;
import org.apache.storm.tuple.Fields;
import org.apache.storm.tuple.Values;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DataSpout extends BaseRichSpout {
private TopologyContext context;
private SpoutOutputCollector collector;
private transient Integer spoutId;
private transient Integer numberOfEmits;
private final Logger logger = LoggerFactory.getLogger(DataSpout.class);
@Override
public void open(Map map, TopologyContext tc, SpoutOutputCollector soc) {
this.context = tc;
this.collector = soc;
this.spoutId = context.getThisTaskId();
this.numberOfEmits = 0;
}
@Override
public void declareOutputFields(OutputFieldsDeclarer ofd) {
String someRandomNumber = "someRandomNumber";//these fields strings have to be exactly the same wherever you specify them
String theSpoutId = "theSpoutId";
String aTupleName = "aTupleName";
ofd.declare(new Fields(aTupleName, someRandomNumber, theSpoutId));
}
@Override
public void nextTuple() {
if (++numberOfEmits < 200) {
Double randNum = Math.floor(Math.random() * 10);
SomeTuple tup = new SomeTuple();
collector.emit(new Values(tup, randNum, spoutId), tup);//(tuple, uniqueID for FieldsGrouping, spoutID), unique messageId
logger.info("Emitting {}",randNum);
}
else {logger.info("NO MORE EMITS");}
StopWatch.pauseMilli(300);
}
@Override
public void ack(Object msgId) {
}
@Override
public void fail(Object msgId) {
}
}
ProcessingBolt.java
package com.sdint.basicstorm;
import java.util.HashMap;
import java.util.Map;
import org.apache.storm.task.OutputCollector;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.topology.base.BaseRichBolt;
import org.apache.storm.tuple.Tuple;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ProcessingBolt extends BaseRichBolt {
private OutputCollector collector;
private transient Integer boltId;
private final Logger logger = LoggerFactory.getLogger(ProcessingBolt.class);
private HashMap
@Override
public void declareOutputFields(OutputFieldsDeclarer ofd) {
}
@Override
public void prepare(Map map, TopologyContext tc, OutputCollector oc) {
collector = oc;
boltId = tc.getThisTaskId();
counter = new HashMap<>();
}
@Override
public void execute(Tuple tuple) {
//From the spout, Values(randNum, spoutId) were sent, so we receive it here
SomeTuple tup = (SomeTuple) tuple.getValue(0);//the first value in the fields
Double num = (Double) tuple.getValue(1);//the second value sent in the fields
Integer spoutId = (Integer) tuple.getValue(2);//the third value sent in the fields
//Similarly, you could have sent more values from the spout and received it as tuple.getValue(3) or (4) etc.
logger.info("Bolt {} received {} from spout {}", boltId, num, spoutId);
CountTheNumberOfSimilarNumbersReceived(num);
DisplayCounts();
collector.ack(tuple);
}
private void CountTheNumberOfSimilarNumbersReceived(final Double num) {
if (counter.containsKey(num)) {
Integer existingCount = counter.get(num);
counter.put(num, ++existingCount);//increment the count for this number
} else {
counter.put(num, 0);
}
}
private void DisplayCounts() {
logger.info("\n\n\n\nBolt {} has", boltId);
for (Map.Entry
logger.info("{} number of {}", oneVal.getValue(), oneVal.getKey());
}
logger.info("\n\n\n");
}
}
- The tuple is the entire bunch of data being emitted. ie: The object that is emitted in the spout (new Values(tup, randNum, spoutId)). That's the same object that reaches the bolt (public void execute(Tuple tuple)).
- The fields are the objects that formed the tuple (SomeTuple object, random number and spout Id)
- Grouping of tuples (sending them to specific bolts) is done based only on the random number field because that's what you specified in the line .fieldsGrouping(spoutName, new Fields(someRandomNumber));
So if random number is 1.0, the tuple might be sent to bolt 5.
If random number is 2.0, the tuple might be sent to bolt 3.
If random number is 2.0 again, the tuple will be sent to bolt 3.
That's all there is to it. If you want, you can even have the random number as the tuple. It could also be a String instead of a number. Any object you want. You can also re-order the fields in the tuple. Just make sure you also have the same order in the declareOutputFields function.
You also need to know that fields grouping does not overwhelm Storm with data.
More on Apache Storm:
17 September 2016
How gravity based water filters purify water
Disclaimer: I am not an expert on chemicals or water filters. What is mentioned in this particular post about chemicals is a perception based on information sourced from the internet.
If you use an RO (Reverse Osmosis) filter and are familiar with the process of osmosis, then all you need to know is that RO is osmosis in the reverse direction which happens when water pressure is high. The heavy metals and salt in the water is retained within the confines of the membrane and pure water (including chlorine) gets across the membrane.
But gravity based filters aren't like that. They depend on just gravity to go through a few filters and viola! somehow, the water is purified. On asking the company how bacteria and viruses are killed, you'll get at best an ambiguous answer. In-fact, an investigation into a certain company's 1 crore viruses kill claim was shown to be exaggerated.
So the first thing you learn about modern water filter companies is:
Don't trust their marketing. Ask extremely detailed questions. If you don't get proper answers, don't buy the product.
Backstory:
My old water filter stopped functioning, and I had to get a new temporary water filter quickly. Looking at the reviews on Amazon, a certain brand's Advanced gravity based water filter seemed ok at that time, because I didn't need to filter borewell water (which would've had metallic impurities). I just needed it to filter the chlorinated corporation water. Ordered it via the company helpline and it was promptly delivered.
After a few days of drinking it, I got an uneasy feeling in my stomach and lost my hunger. It was like the stomach needed time to repair damage it sustained. Oddly, even the construction workers we gave the water to, refused to drink it after a few days.
On reporting it, a company technician came to inspect it, said it might be a problem with the carbon polisher and surprisingly, he himself refused to drink the water saying he's had throat problems after drinking the water at various customers houses. He seemed familiar with the problem.
After replacing the carbon polisher, the problem continued. This time, a different technician arrived, and he refused to drink the water too. Said he was familiar with the problem, replaced the 'germ-kill' kit and asked me to try using the filter again. Problem continued and I had to ask them to take back the product. They took it back after refunding half the price.
What a gravity-based purifier may rely on:
1. A Germkill processor: This is essentially nothing but bleaching powder. The same white powder used to sanitize swimming pools, is used to disinfect your drinking water. This powder basically brings chlorine into the water which kills microorganisms. Problem is, if your water is already chlorinated by your municipal corporation, then the water purifier is just going to add more chlorine and that can be bad for your stomach.
In the case of this product, the germkill processor had this chemical in a little container which would slowly dissolve in the water and when it got over, the red piece of plastic at the top would have moved to the bottom and would be visible to the user as a sign that the germkill kit needed replacement.
2. An advanced microfiber mesh: It's nothing but a porous material which allows water through and does not allow dirt through. Nothing advanced about it. Gets dirty quickly, and the technician advised cleaning it every week. So honestly, it does the job, but well...clean it every week is a reality.
3. Carbon polisher: This is a carbon-based fabric that's supposed to remove pesticides, parasites (really? So the germkill kit doesn't work?) and chlorine, but that also means you need to change your carbon cartridge often because it can't adsorb (not absorb) chemicals forever.
4. Microcharged membrane: It's just another fancy name which claims to remove harmful parasites (really? So the germkill kit and the carbon polisher didn't work yet?) It's just a hollow cylinder with a clump of white wires at the bottom. The technician told me it was to give the water a sweet taste.
![]() |
| outside |
![]() |
| inside |
So that's the process. Chlorinate the water, remove dirt, remove chlorine, add a sweet taste. This is claimed to be "better than boiling", but it'd take a very gullible person to believe that.
The most visible and alarming indicator was that their own technicians were refusing to drink the "purified" water. I wrote to the company requesting them to do further checks and if necessary, initiate a product recall in the interest of public safety.
As for you, if you get municipal corporation water, boiling it would be a much more reliable way to get rid of bacteria. Although boiling doesn't get rid of all pathogens, it does reduce it to a safe level. An acquaintance who worked for a water purifier company informed me that it is the old water filters that used to be much better and more reliable than the modern filters. The modern ones bank on getting money out of you by having annual maintenance contracts and having components like the germkill kit and carbon polisher which need frequent replacement.
We need a better solution than relying on unethical corporations for a basic need such as clean drinking water.
10 September 2016
13 August 2016
How to network at conferences
I came across a post by an entrepreneur on Office Chai about why he stopped attending startup conferences.
He says:
He says:
"And it sure was fun. In the beginning, the a-ha moment used to be spotting a celebrity. “I’m in the same room as Sachin Bansal!”....
And I wasn’t my usual self at these conferences. I’m an engineer, and a bit of an introvert, but a switch used to flick on in my head as soon as I had a conference badge around my neck. I’d be approaching people, exchanging business cards, and talking to anyone who’d listen about my startup.
Except that it never went anywhere"
and then...
"It took me two years (and lots of money in conference fees) to figure out what the problem was. Conferences are horribly artificial ways to meet people. Everyone is trying to meet as many people in as little time as possible, and the result is that interactions are superficial and hackneyed. Real relationship-building time and effort. By trying to meet everyone, you end up meeting no one."
What this entrepreneur mentioned here is one of the classic misconceptions people have when they attend conferences. What I'm going to write below, is not my wisdom, but something I've read about from a source I don't recollect (I think it was Rajesh Shetty's blog). But the words of advice remained in my mind and I've been practicing it too.
Here's what you need to remember:
Everyone who attends conferences and networking events have just one thing in mind: "What's in it for me?". So when you attend with the same thing in mind, how do you expect others to help you, when they are there trying to figure out how others can help them?
When you go to such events, go with this thought in mind "How can I bring value to someone else?" or "How can I truly make myself or my business useful to someone else?".
When you have this mindset, you'll automatically eliminate the conversations that only help you. You'll start thinking from the other person's perspective and truly evaluating whether you will actually be able to help that person or not. When you see that you can help the person and you explain it, the person will obviously see that there is a point in remaining in touch with you.
The relationship is retained and it grows.
Sometimes you may not see any opportunity to add value to anyone, but you will meet people whom you see you have a natural connect with. When you meet these people repeatedly, a familiarity and friendship does evolve.
Networking and conferences are indeed useful when you stop seeing it from the perspective of "What's in it for me?".
06 August 2016
11 July 2016
The better way to get date and time on your photos
On one of the latest Sony cameras, I switched on a feature that imprints the date the photograph was taken, onto the bottom right corner of the photo.
I was shocked at the result.
Sony's software puts a fat orange date stamp on the photo without even antialiasing it. Looks like we've been transported to the 1970's.
Searching for a better alternative led me to a script written by Terdon that makes use of imagemagick to extract exif information from the picture.
See the difference between Sony's orange date stamp and imagemagick's white one.
Here's what to do at your bash terminal:
#!/usr/bin/env bash
## This command will find all image files, if you are using other
## extensions, you can add them: -o "*.foo"
find . -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.tif" -o \
-iname "*.tiff" -o -iname "*.png" |
## Go through the results, saving each as $img
while IFS= read -r img; do
## Find will return full paths, so an image in the current
## directory will be ./foo.jpg and the first dot screws up
## bash's pattern matching. Use basename and dirname to extract
## the needed information.
name=$(basename "$img")
path=$(dirname "$img")
ext="${name/#*./}";
## Check whether this file has exif data
if exiv2 "$img" 2>&1 | grep timestamp >/dev/null
## If it does, read it and add the water mark
then
echo "Processing $img...";
convert "$img" -gravity SouthEast -pointsize 22 -fill white \
-annotate +30+30 %[exif:DateTimeOriginal] \
"$path"/"${name/%.*/.time.$ext}";
## If the image has no exif data, use the creation date of the
## file. CAREFUL: this is the date on which this particular file
## was created and it will often not be the same as the date the
## photo was taken. This is probably not the desired behaviour so
## I have commented it out. To activate, just remove the # from
## the beginning of each line.
# else
# date=$(stat "$img" | grep Modify | cut -d ' ' -f 2,3 | cut -d ':' -f1,2)
# convert "$img" -gravity SouthEast -pointsize 22 -fill white \
# -annotate +30+30 "$date" \
# "$path"/"${name/%.*/.time.$ext}";
fi
done
Change the file to an executable and run it:
Run it in the folder where your images are placed.
Works like a charm!
I was shocked at the result.
Sony's software puts a fat orange date stamp on the photo without even antialiasing it. Looks like we've been transported to the 1970's.
Searching for a better alternative led me to a script written by Terdon that makes use of imagemagick to extract exif information from the picture.
- You can run it via the linux commandline
- It's fast
- Doesn't mess up your existing image and
- Is also configurable
See the difference between Sony's orange date stamp and imagemagick's white one.
Here's what to do at your bash terminal:
sudo apt-get install imagemagick
sudo apt-get install exiv2
Then put this script in a file named watermark.sh:#!/usr/bin/env bash
## This command will find all image files, if you are using other
## extensions, you can add them: -o "*.foo"
find . -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.tif" -o \
-iname "*.tiff" -o -iname "*.png" |
## Go through the results, saving each as $img
while IFS= read -r img; do
## Find will return full paths, so an image in the current
## directory will be ./foo.jpg and the first dot screws up
## bash's pattern matching. Use basename and dirname to extract
## the needed information.
name=$(basename "$img")
path=$(dirname "$img")
ext="${name/#*./}";
## Check whether this file has exif data
if exiv2 "$img" 2>&1 | grep timestamp >/dev/null
## If it does, read it and add the water mark
then
echo "Processing $img...";
convert "$img" -gravity SouthEast -pointsize 22 -fill white \
-annotate +30+30 %[exif:DateTimeOriginal] \
"$path"/"${name/%.*/.time.$ext}";
## If the image has no exif data, use the creation date of the
## file. CAREFUL: this is the date on which this particular file
## was created and it will often not be the same as the date the
## photo was taken. This is probably not the desired behaviour so
## I have commented it out. To activate, just remove the # from
## the beginning of each line.
# else
# date=$(stat "$img" | grep Modify | cut -d ' ' -f 2,3 | cut -d ':' -f1,2)
# convert "$img" -gravity SouthEast -pointsize 22 -fill white \
# -annotate +30+30 "$date" \
# "$path"/"${name/%.*/.time.$ext}";
fi
done
Change the file to an executable and run it:
chmod +x watermark.sh
./watermark.sh
Run it in the folder where your images are placed.
Works like a charm!
03 July 2016
The ingenuity of the humble zipper
"Clasp locker" and "hookless fastener". That's what the zip was called, once-upon-a-time.
See how the clasps get locked. A rather ingenious invention by Whitcomb L. Judson.
The reason I'm writing about it today, is because the zippers of my bag stopped working and I thought I'd have to replace the zipper teeth and the slider.
The person at the shop though, told me I'd only have to change the slider. Turned out that with time, the insides of the slider get worn out and they aren't able to make the zipper teeth clasp together anymore. The teeth are still ok, and you don't have to replace them.
Replacing involves removing some of the stitches that hold the teeth...
...removing the old slider completely, replacing it with a new slider and stitching it back up with a sewing machine.
That's it!
This entire process happened in less than 10 minutes, and cost me Rs.30 and Rs.20 for a large and small slider (stitching charges included).
The next time you worry about throwing away your bag or jacket because of a defective zipper, remember this post. You can get it fixed in a jiffy. If possible, try to get a new slider that matches your bag/jacket. The person at the shop might not have a variety of colours.
See how the clasps get locked. A rather ingenious invention by Whitcomb L. Judson.
Dominique Toussaint's animated gif (CC by-SA 3.0).
The reason I'm writing about it today, is because the zippers of my bag stopped working and I thought I'd have to replace the zipper teeth and the slider.
The person at the shop though, told me I'd only have to change the slider. Turned out that with time, the insides of the slider get worn out and they aren't able to make the zipper teeth clasp together anymore. The teeth are still ok, and you don't have to replace them.
Replacing involves removing some of the stitches that hold the teeth...
...removing the old slider completely, replacing it with a new slider and stitching it back up with a sewing machine.
That's it!
This entire process happened in less than 10 minutes, and cost me Rs.30 and Rs.20 for a large and small slider (stitching charges included).
The next time you worry about throwing away your bag or jacket because of a defective zipper, remember this post. You can get it fixed in a jiffy. If possible, try to get a new slider that matches your bag/jacket. The person at the shop might not have a variety of colours.
01 July 2016
Discovering the unknown
X-rays were discovered by accident. We have never seen electrons. We don't know if light is a particle or a wave because of properties which observed. We don't see or hear many wavelengths which other insects and animals can.
In order for our senses of sight, smell, touch, taste and hearing to be able to receive this data and for the brain to process it, we needed to convert X-rays into something that we could see. Electrons paths into something we could experiment upon and prove.
We really are very limited by our senses.
But what if we created a machine and an AI that was capable of manufacturing sensors which would detect properties of the Earth and Universe which we never discovered, and then translate that into data that we can understand?
We would be able to not just discover so many more dimensions, we might even be able to re-program our DNA to be able to take advantage of that data.
How, is the question.
In order for our senses of sight, smell, touch, taste and hearing to be able to receive this data and for the brain to process it, we needed to convert X-rays into something that we could see. Electrons paths into something we could experiment upon and prove.
We really are very limited by our senses.
But what if we created a machine and an AI that was capable of manufacturing sensors which would detect properties of the Earth and Universe which we never discovered, and then translate that into data that we can understand?
We would be able to not just discover so many more dimensions, we might even be able to re-program our DNA to be able to take advantage of that data.
How, is the question.
14 June 2016
08 June 2016
Conferences I attended at IIM-B
Planning ahead for either entrepreneurship or product management, I had attended a few NSRCEL conferences at IIM-B, which provided some valuable insight into startups.
- Funding for Startups
- Open source for startups
- Analytics for startups
- Sales for startups
- Investor Relations & ESOPs For Startups
- Leveraging digital media for startups
- Effectual marketing for startups
You also get to network during the tea breaks. One thing I learnt eventually about networking, is that it's done with the perspective of "how can I add value for the other person?", rather than "what's in it for me?".
14 May 2016
Subscribe to:
Posts (Atom)

















