Brain + VPS for 30 rubles =?

It's nice when all the necessary little things are at hand: a well-writing pen and notebook, a sharpened pencil, a comfortable mouse, a couple of extra wires, etc. These inconspicuous things do not attract attention, but add a life of comfort. The same story with various mobile and desktop applications: for long screenshots, to reduce image size, to calculate personal finances, dictionaries, translators, converters, etc. Do you have such a VPS - which is inexpensive, always at hand and brings many benefits? No, not the one that you have in your company, but your own, "pocket". We thought that without a small VPS in 2019 it was somehow sad, just like without the usual fountain pen at a lecture. Why be sad? Summer is. Well, like summer. IT summer: sit at home, cut favorite projects without any regret. In general, thought and done.





Communism has come, comrades



He is his own - our VPS for thirty



We read articles from competitors and users who wrote 3-4 years ago about why inexpensive VPS is not needed. Well, right, then VPS "for a penny" was pure marketing and could not offer normal working opportunities. But times are changing, the cost of virtual resources is getting lower and for 30 rubles a month we are ready to offer this:





The tariff is subject to additional technical restrictions, details on the page of our cool offer - VPS for 30 rubles.



Who should use such a virtual server? Yes, almost everyone: beginners, enthusiasts, experienced developers, DIY fans and even some companies.



What is this VPS suitable for?



We think that Habr’s readers will definitely find their own way of applying such a configuration, but they decided to put together their own selection of ideas - otherwise all of a sudden someone needs it, but the men don’t know?





There is such a VPS application in the corporate field. In addition to the already mentioned telephone service, you can implement several interesting pieces. For example:





VPS test drive for 30 rubles - done for you



30 rubles is so small that even reluctance to get a card to pay and test. We are also sometimes so lazy, but this time we did everything for you. Before launching the servers in battle, we conducted a test to check all the details and show what the servers are capable of at this tariff. To make it more interesting, we added extreme sports and checked how this configuration behaves if the density and load exceed the values ​​set by us.



The host was under load of a number of virtual machines that performed various tasks on the processor and actively used the disk subsystem. The goal is to simulate a high placement density and a load comparable or greater than the combat one.



In addition to the constant load, we installed 3 virtual machines collecting synthetic metrics using sysbench, the average results of which were given below, and 50 virtual machines that created additional load. All test virtual machines had the same configuration (1 core, 512 GB RAM, 10 GB SSD), the standard debian 9.6 image, which is offered to users on RUVDS, was chosen as the operating system.



The load was simulated in nature and size comparable to the combat:





Also, as you recall, we had three cars collecting synthetic metrics.



A script was run on each machine cyclically every 15 minutes, which runs standard sysbench tests for the processor, memory and disk.



Script sysbench.sh
#!/bin/bash date +"%Y-%m-%d %H:%M:%S" >> /root/sysbench/results.txt sysbench --test=cpu run >> /root/sysbench/results.txt sysbench --test=memory run >> /root/sysbench/results.txt sysbench --test=fileio --file-test-mode=seqwr run >> /root/sysbench/results.txt sysbench --test=fileio --file-test-mode=seqrd run >> /root/sysbench/results.txt sysbench --test=fileio --file-test-mode=rndrw run >> /root/sysbench/results.txt
      
      







The results are given for convenience in sysbench format, but the average values ​​for the entire time of testing from all machines are taken, the result can be viewed here:



Sysbanch-avg.txt
sysbench 0.4.12: multi-threaded system evaluation benchmark



Running the test with following options:

Number of threads: 1



Doing CPU performance benchmark



Threads started!

Done.



Maximum prime number checked in CPU test: 10000



Test execution summary:

total time: 19.2244s

total number of events: 10000

total time taken by event execution: 19.2104

per-request statistics:

min: 1.43ms

avg: 1.92ms

max: 47.00ms

approx. 95 percentile: 3.02ms



Threads fairness:

events (avg/stddev): 10000.0000/0.00

execution time (avg/stddev): 19.2104/0.00



sysbench 0.4.12: multi-threaded system evaluation benchmark



Running the test with following options:

Number of threads: 1



Doing memory operations speed test

Memory block size: 1K



Memory transfer size: 102400M



Memory operations type: write

Memory scope type: global

Threads started!

Done.



Operations performed: 104857600 (328001.79 ops/sec)



102400.00 MB transferred (320.32 MB/sec)



Test execution summary:

total time: 320.9155s

total number of events: 104857600

total time taken by event execution: 244.8399

per-request statistics:

min: 0.00ms

avg: 0.00ms

max: 139.41ms

approx. 95 percentile: 0.00ms



Threads fairness:

events (avg/stddev): 104857600.0000/0.00

execution time (avg/stddev): 244.8399/0.00



sysbench 0.4.12: multi-threaded system evaluation benchmark



Running the test with following options:

Number of threads: 1



Extra file open flags: 0

128 files, 16Mb each

2Gb total file size

Block size 16Kb

Periodic FSYNC enabled, calling fsync() each 100 requests.

Calling fsync() at the end of test, Enabled.

Using synchronous I/O mode

Doing sequential write (creation) test

Threads started!

Done.



Operations performed: 0 Read, 131072 Write, 128 Other = 131200 Total

Read 0b Written 2Gb Total transferred 2Gb (320.1Mb/sec)

20251.32 Requests/sec executed



Test execution summary:

total time: 6.9972s

total number of events: 131072

total time taken by event execution: 5.2246

per-request statistics:

min: 0.01ms

avg: 0.04ms

max: 96.76ms

approx. 95 percentile: 0.03ms



Threads fairness:

events (avg/stddev): 131072.0000/0.00

execution time (avg/stddev): 5.2246/0.00



sysbench 0.4.12: multi-threaded system evaluation benchmark



Running the test with following options:

Number of threads: 1



Extra file open flags: 0

128 files, 16Mb each

2Gb total file size

Block size 16Kb

Periodic FSYNC enabled, calling fsync() each 100 requests.

Calling fsync() at the end of test, Enabled.

Using synchronous I/O mode

Doing sequential read test

Threads started!

Done.



Operations performed: 131072 Read, 0 Write, 0 Other = 131072 Total

Read 2Gb Written 0b Total transferred 2Gb (91.32Mb/sec)

5844.8 Requests/sec executed



Test execution summary:

total time: 23.1054s

total number of events: 131072

total time taken by event execution: 22.9933

per-request statistics:

min: 0.00ms

avg: 0.18ms

max: 295.75ms

approx. 95 percentile: 0.77ms



Threads fairness:

events (avg/stddev): 131072.0000/0.00

execution time (avg/stddev): 22.9933/0.00



sysbench 0.4.12: multi-threaded system evaluation benchmark



Running the test with following options:

Number of threads: 1



Extra file open flags: 0

128 files, 16Mb each

2Gb total file size

Block size 16Kb

Number of random requests for random IO: 10000

Read/Write ratio for combined random IO test: 1.50

Periodic FSYNC enabled, calling fsync() each 100 requests.

Calling fsync() at the end of test, Enabled.

Using synchronous I/O mode

Doing random r/w test

Threads started!

Done.



Operations performed: 6000 Read, 4000 Write, 12800 Other = 22800 Total

Read 93.75Mb Written 62.5Mb Total transferred 156.25Mb (1341.5Kb/sec)

85.61 Requests/sec executed



Test execution summary:

total time: 152.9786s

total number of events: 10000

total time taken by event execution: 14.1879

per-request statistics:

min: 0.01ms

avg: 1.41ms

max: 210.22ms

approx. 95 percentile: 4.95ms



Threads fairness:

events (avg/stddev): 10000.0000/0.00

execution time (avg/stddev): 14.1879/0.00






The results are indicative, but still they should not be perceived as QoS.



Overload machines



Soft:





Installed MariaDB, Like here :



 apt-get install libmariadbclient-dev mysql -e "INSTALL PLUGIN blackhole SONAME 'ha_blackhole.so';" --   test_employees_sha
      
      





The test base is taken from here :



The base is deployed as indicated here :



 mysql -t < employees.sql mysql -t < test_employees_sha.sql
      
      





Test base of small volume:

Table

RowsCount

Data size (MB)

Index size (KB)

departments

9

0.02

16.00

dept_emp

331143

11.52

5648.00

dept_manager

24

0.02

16.00

employees

299379

14.52

0.00

salaries

2838426

95.63

0.00

titles

442783

19.56

0.00



The primitive test service is written on a knee in python; it performs four operations:



  1. getState: returns status
  2. getEmployee: returns from the employee database (+ salaries, + titles)
  3. patchEmployee: modifies employee fields
  4. insertSalary: performs insert salary


Service source (dbtest.py)
 #!/usr/bin/python import mysql.connector as mariadb from flask import Flask, json, request, abort from mysql.connector.constants import ClientFlag app = Flask(__name__) def getFields(cursor): results = {} column = 0 for d in cursor.description: results[d[0]] = column column = column + 1 return results PAGE_SIZE = 30 @app.route("/") def main(): return "Hello!" @app.route("/employees/<page>", methods=['GET']) def getEmployees(page): offset = (int(page) - 1) * PAGE_SIZE connection = mariadb.connect(user='admin', password='q5XpRomdSr', database='employees') cursor = connection.cursor() cursor.execute("SELECT * FROM employees LIMIT {} OFFSET {}".format(PAGE_SIZE, offset)) return {'employees': [i[0] for i in cursor.fetchall()]} @app.route("/employee/<id>", methods=['GET']) def getEmployee(id): id = int(id) connection = mariadb.connect(user='admin', password='q5XpRomdSr', database='employees') cursor = connection.cursor() cursor.execute("SELECT * FROM employees WHERE emp_no = {}".format(id)) fields = getFields(cursor) employee = {} found = False for row in cursor.fetchall(): found = True employee = { "birth_date": row[fields["birth_date"]], "first_name": row[fields["first_name"]], "last_name": row[fields["last_name"]], "gender": row[fields["gender"]], "hire_date": row[fields["hire_date"]] } if not found: abort(404) cursor.execute("SELECT * FROM salaries WHERE emp_no = {}".format(id)) fields = getFields(cursor) salaries = [] for row in cursor.fetchall(): salary = { "salary": row[fields["salary"]], "from_date": row[fields["from_date"]], "to_date": row[fields["to_date"]] } salaries.append(salary) employee["salaries"] = salaries cursor.execute("SELECT * FROM titles WHERE emp_no = {}".format(id)) fields = getFields(cursor) titles = [] for row in cursor.fetchall(): title = { "title": row[fields["title"]], "from_date": row[fields["from_date"]], "to_date": row[fields["to_date"]] } titles.append(title) employee["titles"] = titles return json.dumps({ "status": "success", "employee": employee }) def isFieldValid(t, v): if t == "employee": return v in ["birdth_date", "first_name", "last_name", "hire_date"] else: return false @app.route("/employee/<id>", methods=['PATCH']) def setEmployee(id): id = int(id) content = request.json print(content) setList = "" data = [] for k, v in content.iteritems(): if not isFieldValid("employee", k): continue if setList != "": setList = setList + ", " setList = setList + k + "=%s" data.append(v) data.append(id) print(setList) print(data) connection = mariadb.connect(user='admin', password='q5XpRomdSr', database='employees', client_flags=[ClientFlag.FOUND_ROWS]) cursor = connection.cursor() cursor.execute("UPDATE employees SET {} WHERE emp_no = %s".format(setList), data) connection.commit() if cursor.rowcount < 1: abort(404) return json.dumps({ "status": "success" }) @app.route("/salary", methods=['PUT']) def putSalary(): content = request.json print(content) connection = mariadb.connect(user='admin', password='q5XpRomdSr', database='employees', client_flags=[ClientFlag.FOUND_ROWS]) cursor = connection.cursor() data = [content["emp_no"], content["salary"], content["from_date"], content["to_date"]] cursor.execute("INSERT INTO salaries (emp_no, salary, from_date, to_date) VALUES (%s, %s, %s, %s)", data) connection.commit() return json.dumps({ "status": "success" }) @app.route("/state", methods=['GET']) def getState(): return json.dumps({ "status": "success", "state": "working" }) if __name__ == '__main__': app.run(host='0.0.0.0',port='5002')
      
      





Attention! In no case should you take this service as an example or a guide!



Tests are performed using the good old JMeter. A series of tests was launched, lasting from 15 minutes to 2 hours, without interruptions, the percentage of requests changed, the throughput ranged from 300 to 600 requests per minute. The number of threads from 50 to 500.



Due to the fact that the base is very small, the command:



 mysql -e "SHOW ENGINE INNODB STATUS"
      
      





Shows that:



 Buffer pool hit rate 923 / 1000, young-making rate 29 / 1000 not 32 / 1000
      
      





The following are average response times:

Label

Average

Median

90% Line

95% Line

99% Line

Min

Max

getEmployee

37.64

12.57

62.28

128.5

497.57

five

4151.78

getState

17

7.57

30.14

58.71

193

3

2814.71

patchEmployee

161.42

83.29

308

492.57

1845.14

five

6639.4

putSalary

167.21

86.93

315.34

501.07

1927.12

7

6722.44



Perhaps it will be difficult for you to judge by these synthetic results how suitable this VPS is for your specific tasks and in general, the above methods are limited to those cases that we had to deal with in one form or another. So our list is clearly not exhaustive. We suggest that you draw your own conclusions and test the server for 30 rubles on your real applications and tasks and suggest your options for such a configuration in the comments.



All Articles