I’ve had a few issues with my current hosting provider over the past few years. I’ve been with them since 2013. Prior to them I had either DreamHost or GoDaddy. I’m uncertain as to the order that I’ve had hosting providers. The primary issue I have is with the outdated version of MySQL. Version 5 reached its end of life in October of last year, and the current version is 8. There are other issues I have, but MySQL has got to be the biggest issue. With this new project, I need to consider where I’d like to actually host. Just about everywhere I look, I’m only seeing LAMP solutions (Linux Apache MySQL, PHP) with a focus on WordPress. Many hosts make it unclear if you can do anything else other than WordPress. Other than that, everything else is either dedicated servers or cloud hosting. Dedicated servers are cost prohibitive, and cloud hosting puts you in a position where you could suddenly be facing a monthly bill of $100K if your app goes viral overnight.
My main focus is longterm goals, so I’m looking mostly at the prices without 1st year deals/promotions. I also want the ability to start small and upgrade as needed.
I don’t know who to trust when I watch videos, read articles, or see a “Top X Web Hosts” list or “Best Hosting Providers of [Year]” list. It seems like everyone is affiliated with every hosting provider out there. Once I see that link, can I trust what they say if they are being paid to say it?
In all of my research, it seems like everyone has a negative comment about every host provider out there. You can’t pick a single host without someone complaining about it without stating specifically why they don’t like it other than customer service or slow response for the time to the first byte (TTFB). Why did they need customer service? I’ll state my issues with my current host.
- MySQL is at an old version, often failing to be compatible with SQL scripts that I write. The most recent issues that I have had were trying to use UUID functions. I had to reduce my queries to use random number generators to simulate the function. I often find answers to solve problems, but then spend unnecessary time translating to an older version of MySQL to work in a similar way.
- PHP had not kept up to date for many years until recently when cPanel offered the ability to change the version. At first, it only supported a few versions, but not the latest. Today, I can choose the latest version of PHP. Things have improved. However, I later found that the session management problem returned and changed my scripts not to use it.
- FTP Accounts can’t be restricted to a specific subdomains folder. I wanted to create an FTP account that had its home directory tied to a websites folder. I’ve tried every thing I could, but ended up not being able to tie the account down to login directly to that folder.
- I can’t do secure FTP (SFTP)
- FTP often had issues if I uploaded too many files at once while deploying a website where I couldn’t upload more than two asynchronously and had to throttle my uploads. FTP client would get disconnected often.
- Subdomains have an odd folder name assigned to them with a random hash. I’d like to have folders that indicate what subdomain or website they are associated with, or provided the ability to rename them. I don’t like seeing a random hash in my file system without context of what website it belongs to.
- Price feels high.
- The 2FA is spamming my email account. I wish they didn’t remove the mobile authenticator version of it.
- They are constantly logging me out.
- Very difficult to use Brotli compressed files.
What do I like about BlueHost?
- Recently, customer service has been amazing, knowledgeable, and understanding of exact details. If I have a problem, you can guarentee I’ve looked up every possible solution on my end. I don’t simply get the first line of support asking if I turned something off and on again – they understand the exact detail of what I’m laying out. This has happened twice within the past year. One was related to how the session cache for PHP was trying to use a temp folder that it didn’t have access to. I had no way to controlling what folder it pointed to or granting permissions.
- CronJobs
- Editable DNS records
- Unlimited email
- Unlimited subdomains
- Free SSL on all subdomains (This used to cost extra for each domain)
- Unlimited databases
- Remote MySQL access
- FTP and unlimited accounts
- GIT – was nice, but I didn’t use it much. More of experimenting.
| Host | BlueHost | Hostinger | HostGator | DreamHost | GoDaddy |
|---|---|---|---|---|---|
| Plan | Premium | Baby | Shared Starter | Web Hosting Economy | |
| Languages | PHP | PHP | PHP 8.2, Ruby on Rails, Perl, Python | PHP? | PHP? |
| Database | MySQL 5.7 | MariaDB 10.11.7 | MySQL | MySQL | MySQL? |
| Space | Unlimited | 100 GB | 10GB | 50GB | 25 GB |
| .com Domain | $22 | $10 | $18 | Included? | Included? $12 |
| Yearly Hosting | $203 | $96 | $204 | $84 | $120 |
| Unlimited | 100 | Yes | Add-on Fee | Add-on | |
| Subdomains | Unlimited | 100 | 2 | 5 | |
| Databases | Unlimited | Unlimited | 6 | 10 | |
| SSL | Included | Included | Included | $99 | |
| Cronjobs | Unlimited | Unlimited | Yes | ||
| Bandwidth | Unlimited | Unlimited | Unlimited | ||
| FTP Accounts | Unlimited | Unlimited | 6 |
| Host | SiteGround | Web.com |
|---|---|---|
| Plan | StartUp | Basic |
| Languages | PHP 7.3 to 8.1 | |
| Database | MySQL 8 & PostgreSQL | |
| Space | 10GB | 10GB |
| .com Domain | $22 | |
| Yearly Hosting | $216 | $120 |
| Unlimited | Yes | |
| Subdomains | Unlimited | |
| Databases | Unlimited < 1GB | |
| SSL | Included | No |
| Cronjobs | Yes | |
| Bandwidth | Unlimited | Unlimited |
| FTP Accounts | Unlimited | 25 |
I ran into an odd issue with Hostingers chatbot. After you ask a few questions, it refuses to let you answer any additional questions.

One of the other things I found out is that there is a big company called Newfold Digital that represents most of these hosting providers.

So if I’m having such a tough time with Bluehost – it seems a bit pointless to go with one of the other brands offered by the same company. That rules out web.com and HostGator.
I’m considering Hostinger. The price is low. I was able to find most information I was looking for. The one concern I do have is that the company seems to raise its prices often – within the same year. I video 3 months ago shows the non-discounted price was at $9.99/month. It’s now at $11.99 – and the video itself was complaining that the prices were raising. If I buy into the hosts plan, what will the price be like when its time to renew?


Looking further, it appears the lower rate is actually based on a four year plan and actually add a setup fee for the monthly plan. This brings up some extra questions. If I decide to upgrade later, will I be pro-rated compared to what I had already paid for? Do the contract periods line up?

And… the transaction was denied?

What is my bank trying to tell me?

I’ve had many fraudulent warnings over the years, but never any that were real. It’s more of a nuisance. I was able to go through PayPay and make the purchase.
So… the interface is different. I’m used to cPanel on most hosts (GoDaddy, DreamHost, BlueHost). Hostinger is different. So far it’s intuitive. However – I didn’t login to anything. Ah, I found where I can set my password – and setup authenticator. Awesome! BlueHost used to support an authenticator for 2FA which worked great with 1 Password filling it in for me, but now they slow down the whole process by spamming my inbox.
Although I can set my DNS records for CNAME, A, etc. I don’t see any MX records. Let’s take a look at setting up email.

Say it ain’t so. I have to pay extra for my own domains email? This is a bait and switch. They said I get 100 email accounts. They said nothing about it not being tied to the domain. Let’s see what cheesy domains they offer.

Well… maybe its not what I think it was. More options to pay money…

It feels like the only benefit of paying money is the spam protection. I don’t see anything about account limits.

I must say I’m impressed with the interface. I’m seeing options that I didn’t have with BlueHost. Like – what is DKIM?

The limits have me concerned if a lot of users start activating accounts or asking for password resets. Only 300 emails per day. And no more that 15,000 emails per account. Do I need to automate a process to delete the sent emails? With only 300 emails permitted to send, it takes 50 days to fill up your account. When attempting to update, they don’t list if there are any increases to the number of emails per day or total number of emails per account. When the time comes, I may need to setup my email elsewhere if I can modify the MX record. Yep – I now have two MX records and I can modify them.
Subdomains… yes. I was able to create a subdomain. It is also secured with a certificate via Let’s Encrypt. Although later I found that subdomains are supposed to be added while looking at the domain – not as a new domain.

File management. Our subdomains have folders that make sense!

Dragon Drops! I can drag files onto the folder. Not just files – folders! And nested folders! I can drag and drop my entire site. With BlueHost, I had to open a different view to drag and drop. I’ve accidentally forgotten to open the view more than once.
Brotli is on! My .htaccess doesn’t do anything in regards to serving images with Brotli compression, yet I’m seeing my ICO file was compressed with Brotli as well.

And it has Etag support out of the box? Whoa. That’s going to save bandwidth.

I can create an FTP account limited to a subdomain!
Unfortunately it doesn’t support secure FTP.
SSH – Yes. Not active by default. Let’s try SFTP with the IP/Port we have… nope. Ah, I figured it out. You have to use the SSH username/password to use SFTP. I was able to login via SFTP and SSH.
Cache Manager… impressive. Although my PHP requests shouldn’t be cached since the API is not a static page. Now if I had a blog, that would be different.
GIT. It appears that it works with both public and private repositories. I don’t push my build files to the repository itself. I’ll need another way to deploy files. Mainly SFTP.
IP Block and White List. Nice…
Hotlink Protection. I’m not sure about this. If I enable it, people will be unable to see my images hosted on other sites. How does that work for progressive web apps?
PHP configuration is nice. They default to version 8.1 but allow me to go up to 8.3 (current). They even have something to enable a Brotli compression extension. I don’t see zstd or gzip. Maybe its a compression library.

Looking around, the one thing that I don’t see is visitor metrics. I found it under Performance / Analytics. I’m seeing 404’s
- /humans.txt
- /ads.txt
- /.well-known/security.txt
- /security.txt
- /sitemap.xml
- /robots.txt
I used Image to ASCII: Free ASCII Art Converter to convert the logo to some ASCII art for the humans.txt page. I also setup everything else except ads.txt.
Remote database connection – supported. Need to provide your own IPv4/IPv6 address to be white listed or choose Any Host.
Once I created a MySQL database, I was able to use phpMyAdmin and run the following query:
select @@version; -- 10.11.7-MariaDB-cll-lve
When I asked, they told me specifically that I would have MySQL version 8. The fact that they sold me one thing and I got something else is a punch to the gut. Yes – it’s AI, but out of all information fed into it for a website, the specific platform and version of the database should have been a specific thing that the AI should have been well aware of without question. It’s not like it was a vague question. It was very specific on what the service was that was being sold to me. Why even have a chatbot if it can’t answer a simple and common question about the product being sold?
What is MariaDB? I see the MariaDB website references the MySQL 5.7 End of Life and mention that Oracle has a lock-in. That makes sense why BlueHost isn’t upgrading. Same compatibility with MySQL. hmm.. good. Can I run queries targeted towards MySQL 8? Let’s do some simple tests…
-- UUID
select uuid();
-- Random Bytes
select hex(random_bytes(2));
-- Regular Expressions
SELECT REGEXP_REPLACE('123-456-7890', '[0-9]{4}$', '****');
-- Recursive Common Table Expressions
with recursive cte as (
select 1 as n
union all
select n + 1 from cte where n < 10
)
select * from cte;
-- JSON
SELECT JSON_OBJECT(
'name', 'John',
'age', 30,
'email', 'john@example.com'
) AS person_json;
-- Window Functions
SELECT n,
ROW_NUMBER() OVER (ORDER BY n)
FROM (
SELECT 1 AS n UNION ALL
SELECT 5 UNION ALL
SELECT 3 UNION ALL
SELECT 8 UNION ALL
SELECT 2
) AS dummy_data;
-- Window Functions with Aggregates
SELECT
SUM(n) OVER (ORDER BY n) AS running_total,
COUNT(n) OVER (ORDER BY n) AS running_count
FROM (
SELECT 1 AS n
UNION ALL
SELECT 5
UNION ALL
SELECT 3
UNION ALL
SELECT 8
UNION ALL
SELECT 2
) AS dummy_data;
Impressive. Just for confirmation, I tried running the tests on BlueHost with MySQL 5.7. Surprisingly the UUID, RANDOM_BYTES, and JSON tests worked on BlueHost. They changed something. UUID and RANDOM_BYTES did not work two months ago. Did they recently polyfill the functions? This has been one of my biggest frustrations as I often find solutions to problems online, but constantly face incompatibility with the current version of MySQL.
I’m noticing that BlueHost and Hostinger both use UUID v1. Actually, MySQL in general uses this version. I’m also noticing a pattern across both hosts if I try to select five of them.
select UUID() union ALL select UUID() union ALL select UUID() union ALL select UUID() union ALL select UUID();
| Host | UUID | Version |
|---|---|---|
| Hostinger | bf92b205-1aa3-11ef-9c93-fecd07b98e98 bf92b23d-1aa3-11ef-9c93-fecd07b98e98 bf92b23f-1aa3-11ef-9c93-fecd07b98e98 bf92b241-1aa3-11ef-9c93-fecd07b98e98 bf92b243-1aa3-11ef-9c93-fecd07b98e98 | 1 |
| BlueHost | ec96daf4-1aa3-11ef-9675-525400058c8c ec96db09-1aa3-11ef-9675-525400058c8c ec96db0d-1aa3-11ef-9675-525400058c8c ec96db10-1aa3-11ef-9675-525400058c8c ec96db13-1aa3-11ef-9675-525400058c8c | 1 |
How do I know it’s version 1? That third set of numbers starts with a 1. Starting in Microsoft shops early in my IT career in the 90’s and early 2000’s, I’m more familiar with version 4 where all other values except the 4 are cryptographically random. I think there are currently 3 proposals of UUID going up to version 8, but my understanding is that everyone uses version 4 due to the randomness. I prefer randomness more than anything else. Since we have repeating numbers – especially between both hosts, I’m assuming time is baked into the first, second and third groups of numbers. Something about the machine may be baked into the last group, and randomness may be part of the first group since that seems to be the part that’s changing. Let’s see what version 1 does.
Version 1
Number of nanoseconds (ÎĽsec) since October 15, 1582 UTC … 1582… isn’t that when the calendar started? Yes. Gregorian calendar. But why not start in February? Ah, I see. Pope Gregory XIII stated it wouldn’t go into effect until October.
Anyway, lets see where we are at with this GUID.
| Low Time | ec96db13 | Timestamp 3 |
| Mid Time | 1aa3 | Timestamp 2 |
| Version + High Time | 11ef | Version 1 + Timestamp 1 |
| Clock Sequence & Variant | 9675 | Used for parallel cpu cycles creating Guids at the same time |
| Node / MAC Address | 525400058c8c | 52:54:00:05:8c:8c |
Our Time is represented in multiple parts. Let’s break down what we have
| Evaluation | JavaScript | Value |
|---|---|---|
| Parts | Low = “ec96db13”; Mid = “1aa3”; High = “1ef”; | ec96db13 1aa3 1ef |
| Hex | Hex = “0x” + High + Mid + Low; | 0x1ef1aa3ec96db13 |
| Timestamp Nanoseconds | Timestamp = parseInt(Hex) | 139,359,404,823,075,600 |
| Epoch | October 15, 1582 12:00:00 AM UTC | |
| Epoch Nanoseconds | EpochNanoseconds = 122192928000000000; | 122,192,928,000,000,000 |
| Nanoseconds | Nanoseconds = Timestamp – EpochNanoseconds | 17,166,476,823,075,600 |
| Milliseconds | Milliseconds = Nanoseconds / 10000 | 1716647682307.56 |
| Date | new Date(Milliseconds) | Sat May 25 2024 10:34:42 GMT-0400 (Eastern Daylight Time) |
| Current Date/Time | new Date() | Sat May 25 2024 11:16:29 GMT-0400 (Eastern Daylight Time) |
So the nice thing is, we could essentially pull out the creation date of a GUID. The bad news is that if the system isn’t setup right, its easier to guess GUIDs made around the same time on the same machine if one of the existing GUIDs is known. The clock variant doesn’t seem to change. If the end-users have the capability to generate GUID’s offline, they could spoof a GUID (or unintentionally create one while following a tutorial for offline content) making it appear that something originated from one of our machines. From a network management or technical troubleshooting perspective, we could identify which machine/host created the GUID. I just don’t like the idea that randomness is out of the equation.
What other benefit would there be to version 1. Sorting? Not really. The time parts are reversed. You’d be sorting by the smallest increment of time first (nanoseconds, milliseconds, then seconds, etc…). Predictability? That seems more like a bad thing.
Since MySQL doesn’t support v4 of uuid with it’s own generation, we need to create our own identifier. To keep it cryptographically random, we can use the RANDOM_BYTES() function to get better randomized values rather than calling RAND(). Guids can be represented as 128 bit numbers (16 bytes). We need an equivalent.
In another situation, we could set everything up to rely on JavaScript for our id generation via crypto.randomUUID().
Nano ID may be the way to go to create our own format. They have a collision calculator. With Nano ID, we can make all 128 bits of a 128 bit number random rather than the 121 random bits in a v4 UUID.
const { customAlphabet } = require('nanoid');
const alphabet = '0123456789abcdef';
const nanoid = customAlphabet(alphabet, 32);
nanoid() //=> "ae8a1226b87e41d2386df769419a3fb1"
Rather than hexadecimal, we could also create our own character set. We are essentially making our own “base” set to represent a random number.
| Description | Alphabet | Size | 1% Collision |
|---|---|---|---|
| Least used letters | zqxjkvbypgfwmucl | 32 | 2,615,321T |
| More letters, smaller char length | abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 `~!@#$%^&*()_+-={}|[]\: | 20 | 2,791,223T |
| Binary | 01 | 36 | 37K |
| DNA | gatc | 36 | 9B |
| Roman Numerals | IVXLCM | 36 | 14T |
| DNA+ | gatcGATC | 36 | 2,554T |
| Octet | 01234567 | 36 | 2,554T |
| Decimal | 0123456789 | 36 | 141,776T |
| Hex | 0123456789abcdef | 32 | 2,615,321T |
| Base64 | abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789 +/ | 22 | 10,461,284T |
The problem with a randomized id is that it’s going to cause the database to have problems with indexing (B-tree sorting) when a new record is inserted at a random location and the data partitions need to be reshuffled. What we could do is prefix our key with time (yyyy-dd-mm) followed by the random values. That way each day the btrees are rebuilding smaller nodes. The other option is to have a sequential primary key internally that the end-user never sees, and leave the external id as a random set of characters that is used to interact with API’s.
Rather than prefixing the key with a readable 8 character date stamp (64 bits), we could reduce it down to 18 bits. Let’s play with what we can pack into 128 bits.
| Part | Bits | Range | Translated |
|---|---|---|---|
| Days | 16 | 0 to 65535 | May 25, 2024 to Oct 31, 2203 (179.5 years) |
| Version | 4 | 0 to 15 | Version 1 to 16 |
| Variance | 4 | 0 to 15 | Custom. Could be used to indicate machine, data center, environment (dev, test, staging, production) or source (internal, third party, anonymous) |
| Randomness | 104 | 638T IDs in one day for a 1% collision |
- Largest portions of date parts stored first (Year-Month-Day) makes it sortable. YMD is based on UTC Epoch.
- Time is explicitly not stored.
- Version is not first, because multiple versions may still be in use while switching to a new version. This allows minimal impact to sorting/indexing during that changeover. Version was padded to 6 bits so that randomness would be evenly divisible by 8.
- 104 bits (8 bytes) dedicated to randomness, allowing 638 trillion ID generations in one day to introduce a 1% collision.
- A day of zero could represent that the day is unknown if the ID related to some form of data entry.
- A month of 12 or later could represent that the month is unknown. It could also be used to represent a quarter rather than a month:
- 12 – Jan to Mar
- 13 – Apr to Jun
- 14 – Jul to Aug
- 15 – Sep to Dec
- Quarters should be interleaved to improve the sorting, but then the raw data is a bit harder to decode.
- 0 – Quarter 1
- 1 – Jan
- 2 – Feb
- 3 – Mar
- 4 – Quarter 2
- 5 – Apr
- 6 – May
- 7 – Jun
- 8 – Quarter 3
- 9 – Jul
- 10 – Aug
- 11 – Sep
- 12 – Quarter 4
- 13 – Oct
- 14 – Nov
- 15 – Dec
- System only works until ID’s are generated in the year 2535
So we know what the underlying data of our ID will represent. We could represent it as a string separating the parts. In hex, it would look something like this:
0000-0-0-00000000000000000000000000
days-version-variance-random
Internally, we could create a separate table for guids in our database along with a primary sequential primary key. This would let us index the 16 bit days before looking up the random id. This makes the guids sort of like a DNS server. They are not the internal id. They are only used to lookup what the ID is in our own system.
drop table if exists identity_translation;
-- Create a table to hold external and internal ids
CREATE TABLE identity_translation (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
external_id BINARY(16),
UNIQUE KEY unique_external_id (external_id)
);
DELIMITER //
CREATE TRIGGER validate_external_id BEFORE INSERT ON identity_translation
FOR EACH ROW
BEGIN
DECLARE external_id VARCHAR(32);
SET external_id = LPAD(HEX(NEW.external_id), 32, '0');
IF CONV(MID(external_id, 1, 4), 16, 10) > DATEDIFF(CURDATE(), '2024-05-25') THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Invalid external_id: Future dates not permitted.';
END IF;
IF CONV(MID(external_id, 5, 1), 16, 10) != 1 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Invalid external_id: Version not supported.';
END IF;
END//
DELIMITER ;
-- Create a new identity
INSERT IGNORE INTO identity_translation (external_id)
VALUES (
UNHEX(CONCAT(
-- days since May 25, 2024
LPAD(CONV(DATEDIFF(CURDATE(), '2024-05-25') & 0xFFFF, 10, 16), 4, '0'),
-- version
HEX(1),
-- variance
HEX(2),
-- entropy
LPAD(HEX(RANDOM_BYTES(13)),26,'0')
))
);
/*
-- VALIDATION TEST: Insert an identity with a date in the future
INSERT IGNORE INTO identity_translation (external_id)
VALUES (
UNHEX(CONCAT(
-- tomorrow
LPAD(
CONV(
DATEDIFF(
DATE_ADD(CURDATE(), INTERVAL 1 DAY),
'2024-05-25'
) & 0xFFFF,
10,
16
),
4,
'0'
),
-- version
HEX(1),
-- variance
HEX(2),
-- entropy
LPAD(HEX(RANDOM_BYTES(13)),26,'0')
))
);
*/
/*
-- VALIDATION TEST: Insert an identity with an unsupported version
INSERT IGNORE INTO identity_translation (external_id)
VALUES (
UNHEX(CONCAT(
-- days since May 25, 2024
LPAD(
CONV(
DATEDIFF(CURDATE(), '2024-05-25') & 0xFFFF,
10,
16
),
4,
'0'
),
-- version
HEX(2),
-- variance
HEX(2),
-- entropy
LPAD(HEX(RANDOM_BYTES(13)),26,'0')
))
);
*/
-- Select the formatted identity
SELECT
id,
external_id,
CONCAT(
MID(LPAD(HEX(external_id), 32, '0'), 1, 4), '-',
MID(LPAD(HEX(external_id), 32, '0'), 5, 1), '-',
MID(LPAD(HEX(external_id), 32, '0'), 6, 1), '-',
SUBSTRING(LPAD(HEX(external_id), 32, '0'), 7)
) as formatted_id
FROM
identity_translation;
-- Insert a formatted id
INSERT IGNORE INTO identity_translation (external_id)
VALUES (
UNHEX(
REPLACE('0001-1-2-825E546CA4BEC228A1C0BA067B', '-', '')
)
);
-- Check if a formatted id exists
SELECT *
FROM identity_translation
WHERE
external_id = UNHEX(
REPLACE('0001-1-2-825E546CA4BEC228A1C0BA067B', '-', '')
);
Internally, we are using 64 bits sequentially to store a 128 bit id. However, the 128 bit id is focused on randomness so that any client can create an id.
Am I introducing way too much overhead? Maintaining a separate table to translate identities seems like it’s going to result in more complex queries and extra trips for the id resolution. Why am I going out on a limb creating my own format if the industry already uses uuid v1? How am I any better if I haven’t dedicated a lot of time and effort researching this problem, testing and verifying the integrity.
Perhaps for now, I’ll stick with storing uuid v1 in binary(16) to keep it small. I just don’t like the sequential aspect of it. Or… let’s just create our own uuid v4 function.
DELIMITER //
CREATE FUNCTION IF NOT EXISTS uuid4()
-- hhhhhhhh-hhhh-4hhh-[8-b]hhh-hhhhhhhhhhhh
RETURNS CHAR(36) NOT DETERMINISTIC
BEGIN
DECLARE id CHAR(30);
DECLARE variant CHAR(1);
SET id = LOWER(LPAD(HEX(RANDOM_BYTES(15)), 30, '0'));
-- Ensure bits of nibble are Msb0 = 1, Msb1 = 0 (8,9,a,b)
set variant = LOWER(HEX(8 + MOD(ORD(RANDOM_BYTES(1)), 4)));
RETURN INSERT(
INSERT(
INSERT(
INSERT(
INSERT(id, 19, 0, '-'),
16, 0, variant
),
16, 0, '-'
),
13, 0, '-4'
),
9, 0, '-'
);
END//
DELIMITER ;
Hopefully that’s good enough that people don’t call me out on complexity or overlooking randomness with pseudo random number generators (PRNG). The multiple use of INSERT statements feels a bit complex, but it allows me to just create one random value … (not including the variant). From what I understand, RANDOM_BYTES()gets its values from the systems cryptographic random number generator (ie OpenSSL).
In Summary
We have an official website, periplux.io hosted with Hostinger. Many of the issues I have with BlueHost have been resolved with the new host. There are concerns regarding costs and impact once the site becomes popular.
